<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alan from Fwrd</title>
    <description>The latest articles on DEV Community by Alan from Fwrd (@komi_clem_37be40266eae89f).</description>
    <link>https://dev.to/komi_clem_37be40266eae89f</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229022%2F6202ec42-56a4-441b-af89-6835afdb1147.png</url>
      <title>DEV Community: Alan from Fwrd</title>
      <link>https://dev.to/komi_clem_37be40266eae89f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/komi_clem_37be40266eae89f"/>
    <language>en</language>
    <item>
      <title>Forward iPhone SMS to a webhook, with a 30-line receiver</title>
      <dc:creator>Alan from Fwrd</dc:creator>
      <pubDate>Thu, 05 Nov 2026 16:57:04 +0000</pubDate>
      <link>https://dev.to/komi_clem_37be40266eae89f/forward-iphone-sms-to-a-webhook-with-a-30-line-receiver-32bp</link>
      <guid>https://dev.to/komi_clem_37be40266eae89f/forward-iphone-sms-to-a-webhook-with-a-30-line-receiver-32bp</guid>
      <description>&lt;p&gt;iPhones don't let apps read your texts, so there's no SMS API to call. What iOS does have is Shortcuts: a &lt;strong&gt;Message automation&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;I make &lt;a href="https://www.fwrdsms.com" rel="noopener noreferrer"&gt;Fwrd&lt;/a&gt;, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What your endpoint receives
&lt;/h2&gt;

&lt;p&gt;One &lt;code&gt;POST&lt;/code&gt; per forwarded text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/fwrd?token=…&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;6F9619FF-8B86-D011-B42D-00C04FC964FF&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Your verification code is 482913."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Your verification code is 482913."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;content&lt;/code&gt; is the message exactly as it arrived, line breaks kept, nothing added.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;text&lt;/code&gt; is the same string again, so services that read &lt;code&gt;text&lt;/code&gt; (Mattermost, Rocket.Chat incoming webhooks) work without a mapping step.&lt;/li&gt;
&lt;li&gt;If you turn on &lt;strong&gt;Include Who Sent It&lt;/strong&gt; in Fwrd's settings and set the Sender field on its Shortcuts action, a &lt;code&gt;sender&lt;/code&gt; field is added. It's off by default.&lt;/li&gt;
&lt;li&gt;Answer with any &lt;code&gt;2xx&lt;/code&gt; and the text counts as delivered. On a &lt;code&gt;429&lt;/code&gt;, a &lt;code&gt;5xx&lt;/code&gt; or a dropped connection it retries at most twice, with the &lt;strong&gt;same&lt;/strong&gt; &lt;code&gt;Idempotency-Key&lt;/code&gt;, so dedupe on that.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A receiver in a Cloudflare Worker
&lt;/h2&gt;

&lt;p&gt;Workers give you an &lt;code&gt;https://&lt;/code&gt; address on a public hostname for free, which is all the iPhone needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;!&lt;/span&gt;&lt;span class="se"&gt;\d)(\d{3,4})[&lt;/span&gt;&lt;span class="sr"&gt; -&lt;/span&gt;&lt;span class="se"&gt;]?(\d{3,4})(?!\d)&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;!&lt;/span&gt;&lt;span class="se"&gt;\d)(\d{4,8})(?!\d)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/fwrd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FWRD_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CODE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Your work goes here: store it, post it on, start a workflow.&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the secret with &lt;code&gt;wrangler secret put FWRD_TOKEN&lt;/code&gt;, deploy, and paste &lt;code&gt;https://&amp;lt;worker&amp;gt;.&amp;lt;you&amp;gt;.workers.dev/fwrd?token=&amp;lt;FWRD_TOKEN&amp;gt;&lt;/code&gt; 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 &lt;code&gt;curl&lt;/code&gt; script that sends exactly what the iPhone sends, are in &lt;a href="https://github.com/alanwtom/fwrd-sms-webhook-examples" rel="noopener noreferrer"&gt;fwrd-sms-webhook-examples&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  No code: n8n, Zapier or Make
&lt;/h2&gt;

&lt;p&gt;All three take the same JSON with their own webhook trigger:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n:&lt;/strong&gt; a Webhook node set to POST; the text is at &lt;code&gt;$json.body.content&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zapier:&lt;/strong&gt; Webhooks by Zapier, "Catch Hook"; the &lt;code&gt;content&lt;/code&gt; field shows up after one test text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make:&lt;/strong&gt; a Custom webhook module; send one text so it learns the structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The examples repo has an importable n8n workflow and step-by-step notes for the other two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the iPhone side
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://apps.apple.com/app/apple-store/id6759069713?pt=128544281&amp;amp;ct=devto-webhook&amp;amp;mt=8" rel="noopener noreferrer"&gt;Fwrd&lt;/a&gt;, choose &lt;strong&gt;Webhook&lt;/strong&gt;, and paste your URL.&lt;/li&gt;
&lt;li&gt;Choose which texts to forward (a word like &lt;code&gt;code&lt;/code&gt;, or everything).&lt;/li&gt;
&lt;li&gt;Add the ready-made shortcut it offers and switch the automation on. On iOS 27 that's one switch in Shortcuts; the &lt;a href="https://www.fwrdsms.com/guides/webhook" rel="noopener noreferrer"&gt;webhook guide&lt;/a&gt; has the iOS 26 steps and a short video.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Fwrd is free for 15 forwards a month; Fwrd Pro removes the limit. Questions about the payload or a receiver you're building: &lt;a href="mailto:support@fwrdsms.com"&gt;support@fwrdsms.com&lt;/a&gt;, or the issues on the examples repo.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>webhooks</category>
      <category>automation</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
