iPhones don't let apps read your texts, so there's no SMS API to call. What iOS does have is Shortcuts: a Message automation can run an action every time a text arrives, even with the phone locked. Point that action at an HTTPS endpoint and every new text becomes a POST request you can do anything with.
I make Fwrd, an iPhone app whose Shortcuts action does exactly this: it posts each text to a webhook you own (or to Slack, Discord, Telegram, Teams, email and a few others). There's no Fwrd account and no Fwrd server in between; the iPhone sends the request straight to your URL. This post is the developer side: what arrives, and how to catch it.
What your endpoint receives
One POST per forwarded text:
POST /fwrd?token=… HTTP/1.1
Content-Type: application/json
Idempotency-Key: 6F9619FF-8B86-D011-B42D-00C04FC964FF
{"content":"Your verification code is 482913.","text":"Your verification code is 482913."}
-
contentis the message exactly as it arrived, line breaks kept, nothing added. -
textis the same string again, so services that readtext(Mattermost, Rocket.Chat incoming webhooks) work without a mapping step. - If you turn on Include Who Sent It in Fwrd's settings and set the Sender field on its Shortcuts action, a
senderfield is added. It's off by default. - Answer with any
2xxand the text counts as delivered. On a429, a5xxor a dropped connection it retries at most twice, with the sameIdempotency-Key, so dedupe on that. - There's no auth header. Put a long random token in the URL; the whole URL is stored in the iPhone's Keychain. Redirects are refused, so give the final address.
A receiver in a Cloudflare Worker
Workers give you an https:// address on a public hostname for free, which is all the iPhone needs.
const CODE = /(?<!\d)(\d{3,4})[ -]?(\d{3,4})(?!\d)|(?<!\d)(\d{4,8})(?!\d)/;
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (request.method !== "POST" || url.pathname !== "/fwrd") {
return new Response(null, { status: 404 });
}
if (url.searchParams.get("token") !== env.FWRD_TOKEN) {
return new Response(null, { status: 401 });
}
const { content, sender } = await request.json();
const m = content.match(CODE);
const code = m ? (m[3] ?? m[1] + m[2]) : null;
// Your work goes here: store it, post it on, start a workflow.
console.log(JSON.stringify({
key: request.headers.get("Idempotency-Key"),
sender: sender ?? null,
code,
content,
}));
return Response.json({ ok: true });
},
};
Set the secret with wrangler secret put FWRD_TOKEN, deploy, and paste https://<worker>.<you>.workers.dev/fwrd?token=<FWRD_TOKEN> into Fwrd's webhook destination. The full version, which also sends each text on to a second webhook, plus Node and Python servers and a curl script that sends exactly what the iPhone sends, are in fwrd-sms-webhook-examples.
No code: n8n, Zapier or Make
All three take the same JSON with their own webhook trigger:
-
n8n: a Webhook node set to POST; the text is at
$json.body.content. -
Zapier: Webhooks by Zapier, "Catch Hook"; the
contentfield shows up after one test text. - Make: a Custom webhook module; send one text so it learns the structure.
The examples repo has an importable n8n workflow and step-by-step notes for the other two.
Setting up the iPhone side
- Install Fwrd, choose Webhook, and paste your URL.
- Choose which texts to forward (a word like
code, or everything). - Add the ready-made shortcut it offers and switch the automation on. On iOS 27 that's one switch in Shortcuts; the webhook guide has the iOS 26 steps and a short video.
From then on, matching texts reach your endpoint on their own, with the phone unlocked or locked. After the iPhone restarts it has to be unlocked once before forwarding carries on, and the phone needs to be on and online, because the phone itself sends each request.
Fwrd is free for 15 forwards a month; Fwrd Pro removes the limit. Questions about the payload or a receiver you're building: support@fwrdsms.com, or the issues on the examples repo.
Top comments (0)