<?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: Small Business Autopilot</title>
    <description>The latest articles on DEV Community by Small Business Autopilot (@smallbizautopilot).</description>
    <link>https://dev.to/smallbizautopilot</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%2F4199115%2Fe7a7ccdc-8f82-4eb9-8de9-6d466a5ce484.png</url>
      <title>DEV Community: Small Business Autopilot</title>
      <link>https://dev.to/smallbizautopilot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/smallbizautopilot"/>
    <language>en</language>
    <item>
      <title>Missed-call text back in n8n and Twilio: why the Dial action beats the status callback (free workflow JSON)</title>
      <dc:creator>Small Business Autopilot</dc:creator>
      <pubDate>Wed, 21 Oct 2026 20:31:05 +0000</pubDate>
      <link>https://dev.to/smallbizautopilot/missed-call-text-back-in-n8n-and-twilio-why-the-dial-action-beats-the-status-callback-free-3l08</link>
      <guid>https://dev.to/smallbizautopilot/missed-call-text-back-in-n8n-and-twilio-why-the-dial-action-beats-the-status-callback-free-3l08</guid>
      <description>&lt;p&gt;A "missed-call text back" is simple to describe: when a call to a business number isn't answered, text the caller something like &lt;em&gt;"Sorry we missed you, what can we help with?"&lt;/em&gt; Most tutorials build it on Twilio's &lt;strong&gt;call status callback&lt;/strong&gt; and filter for &lt;code&gt;no-answer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That works until the business owner does the obvious thing and &lt;strong&gt;forwards the Twilio number to their mobile&lt;/strong&gt;. If they don't pick up, the mobile's voicemail answers. From Twilio's point of view the forwarded call was &lt;em&gt;answered&lt;/em&gt;, so the status is &lt;code&gt;completed&lt;/code&gt;, and the automation never fires.&lt;/p&gt;

&lt;p&gt;This post walks through an n8n workflow that avoids that problem by using the &lt;code&gt;&amp;lt;Dial&amp;gt;&lt;/code&gt; verb's &lt;code&gt;action&lt;/code&gt; callback with a short timeout. Twilio decides what counts as "missed," and n8n replies with TwiML in the same request. The full workflow JSON is linked at the end. It uses only core n8n nodes plus the official Twilio and Google Sheets nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Caller ──► Twilio number ──► TwiML Bin: &amp;lt;Dial timeout="12" action="…/webhook/missed-call"&amp;gt;
                                   │ rings your mobile
                                   ▼
                    Dial ends (completed | no-answer | busy | failed)
                                   │ POST (form-encoded) to action URL
                                   ▼
n8n: Webhook ─► Settings ─► Respond to Webhook (TwiML) ─► IF missed? ─► IF business hours?
                                                             │              ├─► Daytime text ─┐
                                                             │              └─► After-hours ──┤
                                                             └─► NoOp (answered)              ▼
                                                                  Twilio: send SMS ─► Set row ─► Google Sheets append
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. Forward calls with a TwiML Bin
&lt;/h2&gt;

&lt;p&gt;In the Twilio Console, create a &lt;strong&gt;TwiML Bin&lt;/strong&gt; and set your number's &lt;em&gt;A call comes in&lt;/em&gt; handler to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Response&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Dial&lt;/span&gt; &lt;span class="na"&gt;timeout=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"https://YOUR-N8N-HOST/webhook/missed-call"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    +1YOURMOBILE
  &lt;span class="nt"&gt;&amp;lt;/Dial&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Response&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;timeout&lt;/code&gt; plus Twilio's buffer must be shorter than your voicemail delay.&lt;/strong&gt; Twilio's docs note that it adds about five seconds to the &lt;code&gt;timeout&lt;/code&gt; you set, so &lt;code&gt;12&lt;/code&gt; rings for roughly 17 seconds. Many mobiles send calls to voicemail after 20–25 seconds, but check yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With an &lt;code&gt;action&lt;/code&gt; URL, your response controls the rest of the call.&lt;/strong&gt; Any verbs after &lt;code&gt;&amp;lt;Dial&amp;gt;&lt;/code&gt; in the bin never run, because Twilio executes the TwiML returned by the action URL instead. That's why the n8n workflow has to respond with TwiML.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Twilio POSTs the action request as &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;. The fields we care about are &lt;code&gt;DialCallStatus&lt;/code&gt;, &lt;code&gt;From&lt;/code&gt;, &lt;code&gt;To&lt;/code&gt;, &lt;code&gt;CallSid&lt;/code&gt; and &lt;code&gt;AccountSid&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Webhook node: respond later, from a node
&lt;/h2&gt;

&lt;p&gt;Webhook node settings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP Method&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;&lt;code&gt;missed-call&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Respond&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Using 'Respond to Webhook' Node&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;n8n parses the form body into &lt;code&gt;$json.body&lt;/code&gt;, so after the webhook an item looks roughly like this (values are placeholders):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"x-twilio-signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/x-www-form-urlencoded"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AccountSid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CallSid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"From"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+15555550123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"To"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+15555550100"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"DialCallStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no-answer"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&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;h2&gt;
  
  
  3. One Set node for everything the user edits
&lt;/h2&gt;

&lt;p&gt;Put every user-editable value in a single &lt;strong&gt;Set&lt;/strong&gt; node (v3.4) with &lt;em&gt;Include Other Input Fields&lt;/em&gt; turned on, so &lt;code&gt;body&lt;/code&gt; passes through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;businessName      [Your Business Name]
bookingLink       https://example.com/book
twilioNumber      +15555550100
twilioAccountSid  ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
openHour          8
closeHour         18
voiceMessage      Sorry we missed your call. We'll text you in a moment.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing downstream hardcodes a value, which also makes the workflow easy to share without leaking anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Answer Twilio immediately with TwiML
&lt;/h2&gt;

&lt;p&gt;Twilio is waiting on the line, so respond before doing anything slow. &lt;strong&gt;Respond to Webhook&lt;/strong&gt; with &lt;em&gt;Respond With: Text&lt;/em&gt;, header &lt;code&gt;Content-Type: text/xml&lt;/code&gt;, and this body expression:&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="p"&gt;{{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-answer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;busy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DialCallStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;Response&amp;gt;&amp;lt;Say&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;voiceMessage&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/Say&amp;gt;&amp;lt;Hangup/&amp;gt;&amp;lt;/Response&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;Response&amp;gt;&amp;lt;Hangup/&amp;gt;&amp;lt;/Response&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Respond to Webhook passes its input items through, so the workflow carries on after the HTTP response has gone back to Twilio. The caller hears the short message and hangs up while n8n sends the text.&lt;/p&gt;

&lt;p&gt;If you'd rather offer voicemail in Twilio, swap the &lt;code&gt;&amp;lt;Say&amp;gt;&lt;/code&gt; for &lt;code&gt;&amp;lt;Say&amp;gt;…&amp;lt;/Say&amp;gt;&amp;lt;Record maxLength="60"/&amp;gt;&lt;/code&gt;. Because Twilio is now handling voicemail, nothing is lost by keeping the forward timeout short.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Gate the rest of the workflow
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;IF&lt;/strong&gt; node (v2) with four AND-ed conditions:&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="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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AccountSid&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;            &lt;span class="c1"&gt;// equals {{ $json.twilioAccountSid }}&lt;/span&gt;
&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-answer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;busy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DialCallStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;   &lt;span class="c1"&gt;// is true&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;From&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;                  &lt;span class="c1"&gt;// starts with "+"&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;From&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;                  &lt;span class="c1"&gt;// not equal to "+266696687"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The status check drops &lt;code&gt;completed&lt;/code&gt; (answered) calls.&lt;/li&gt;
&lt;li&gt;The two &lt;code&gt;From&lt;/code&gt; checks drop withheld caller IDs. Twilio's changelog says these now arrive as &lt;code&gt;anonymous&lt;/code&gt; in the &lt;code&gt;From&lt;/code&gt; field. Older behaviour, still seen in forum posts, used the pseudo-number &lt;code&gt;+266696687&lt;/code&gt;, so the workflow filters both.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;AccountSid&lt;/code&gt; check stops casual noise from anyone who finds the URL. &lt;strong&gt;It is not authentication.&lt;/strong&gt; An Account SID is an identifier, not a secret. For real protection, validate Twilio's signature (see &lt;em&gt;Hardening&lt;/em&gt; below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The false branch goes to a &lt;strong&gt;No Operation&lt;/strong&gt; node, so answered calls end cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Business hours and the message
&lt;/h2&gt;

&lt;p&gt;A second IF compares the current hour against the settings:&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="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;$now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openHour&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;$now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closeHour&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$now&lt;/code&gt; uses the &lt;strong&gt;workflow time zone&lt;/strong&gt; (Workflow → Settings), not necessarily the server's. Set it, or your "after hours" text goes out at lunchtime.&lt;/p&gt;

&lt;p&gt;Each branch is a Set node that writes &lt;code&gt;smsText&lt;/code&gt;, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hi, this is {{ $json.businessName }}. Sorry we missed your call! We're probably with a customer.
What can we help with? Reply here or book online: {{ $json.bookingLink }}. Reply STOP to opt out.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both branches feed one &lt;strong&gt;Twilio&lt;/strong&gt; node (&lt;em&gt;Resource: SMS, Operation: Send&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;From:    {{ $json.twilioNumber }}
To:      {{ $json.body.From }}
Message: {{ $json.smsText }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep messages under 160 GSM-7 characters to stay in one segment. A curly apostrophe or an emoji switches the encoding to UCS-2 and drops the per-segment limit to 70 characters.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Log with paired items
&lt;/h2&gt;

&lt;p&gt;After the Twilio node, &lt;code&gt;$json&lt;/code&gt; is Twilio's API response (the message resource), not the call. Use n8n's paired-item lookup to reach back to earlier nodes when building the log row:&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="nx"&gt;Time&lt;/span&gt;         &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;$now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yyyy-LL-dd HH:mm&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="nx"&gt;Caller&lt;/span&gt;       &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missed call from Twilio?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;From&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="nx"&gt;Status&lt;/span&gt;       &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missed call from Twilio?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DialCallStatus&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;sent&lt;/span&gt;    &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Send text to caller&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="nx"&gt;Called&lt;/span&gt; &lt;span class="nx"&gt;back&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That Set node has &lt;em&gt;Include Other Input Fields&lt;/em&gt; off, so only these five columns reach &lt;strong&gt;Google Sheets → Append Row&lt;/strong&gt;. The node uses &lt;em&gt;Map Automatically&lt;/em&gt; against a &lt;code&gt;Missed calls&lt;/code&gt; tab with matching headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing without making a phone call
&lt;/h2&gt;

&lt;p&gt;Click &lt;strong&gt;Listen for test event&lt;/strong&gt; on the Webhook node and POST a fake Dial result to the &lt;em&gt;test&lt;/em&gt; URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://YOUR-N8N-HOST/webhook-test/missed-call"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"AccountSid=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"From=+15555550123"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"To=+15555550100"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"CallSid=CAtest"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"DialCallStatus=no-answer"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get the &lt;code&gt;&amp;lt;Response&amp;gt;&amp;lt;Say&amp;gt;…&lt;/code&gt; TwiML back. Use &lt;code&gt;--data-urlencode&lt;/code&gt;, not &lt;code&gt;-d&lt;/code&gt;. In form encoding, a bare &lt;code&gt;+&lt;/code&gt; decodes to a space, so &lt;code&gt;-d "From=+1555…"&lt;/code&gt; arrives as &lt;code&gt;" 1555…"&lt;/code&gt; and fails the &lt;code&gt;startsWith('+')&lt;/code&gt; check. Real Twilio requests encode it correctly. Hand-rolled tests are where this bites.&lt;/p&gt;

&lt;p&gt;Then test end-to-end with real calls from a second phone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Answer the call. Expect no text.&lt;/li&gt;
&lt;li&gt;Let it ring out. Expect the text and a new row.&lt;/li&gt;
&lt;li&gt;Hang up after a couple of rings. See the caveat below.&lt;/li&gt;
&lt;li&gt;Call outside business hours. Expect the after-hours text.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Caveats worth knowing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caller hangs up before the timeout.&lt;/strong&gt; Twilio's &lt;code&gt;&amp;lt;Dial&amp;gt;&lt;/code&gt; docs say that if the initial caller hangs up, the session ends and the rest of the TwiML doesn't execute. Whether your &lt;code&gt;action&lt;/code&gt; URL still receives a request in that case is something to verify on your own number (test 3 above). If it doesn't, add the number's &lt;strong&gt;call status callback&lt;/strong&gt; as a second trigger for the parent call and deduplicate on &lt;code&gt;CallSid&lt;/code&gt; before texting. That catches callers who give up quickly, which are often the ones you most want to reach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Landlines&lt;/strong&gt; can't receive SMS. Twilio usually accepts the API request, and the message then fails delivery afterwards, which you'll see in Twilio's message logs. The row still gets logged, so you still call them back. If you validate numbers first with Twilio Lookup, set the Twilio node's &lt;em&gt;On Error&lt;/em&gt; to &lt;em&gt;Continue&lt;/em&gt; so a rejected number doesn't stop the log step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;US texting compliance.&lt;/strong&gt; Sending from a 10-digit US long code needs A2P 10DLC registration, and toll-free numbers need toll-free verification. Until that's approved, messages may be filtered. Keep the opt-out line in the text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't reuse these numbers for marketing.&lt;/strong&gt; A one-to-one reply to someone who just called is expected. Bulk promotional texting has separate consent rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hardening: validate &lt;code&gt;X-Twilio-Signature&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Twilio signs every webhook request. To check the signature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take the full URL Twilio called.&lt;/li&gt;
&lt;li&gt;Append each POST parameter's name and value, sorted alphabetically by name, with no delimiters.&lt;/li&gt;
&lt;li&gt;Compute an HMAC-SHA1 of that string using your &lt;strong&gt;Auth Token&lt;/strong&gt; as the key, and Base64-encode the result.&lt;/li&gt;
&lt;li&gt;Compare it with the &lt;code&gt;x-twilio-signature&lt;/code&gt; header.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The n8n Crypto node's HMAC action doesn't offer SHA1 in current versions, so use a &lt;strong&gt;Code&lt;/strong&gt; node (Run Once for Each Item) right after the Webhook:&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;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://YOUR-N8N-HOST/webhook/missed-call&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// exactly what Twilio calls&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// see note below&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&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;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;json&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;span class="nx"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;signatureValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;$json&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-twilio-signature&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add &lt;code&gt;{{ $json.signatureValid }}&lt;/code&gt; &lt;em&gt;is true&lt;/em&gt; to the IF node. On n8n Cloud, &lt;code&gt;crypto&lt;/code&gt; is available in the Code node. On a self-hosted instance, you have to allow the built-in module first (&lt;code&gt;NODE_FUNCTION_ALLOW_BUILTIN=crypto&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Two gotchas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The URL must be exactly what Twilio called. Behind a reverse proxy, make sure n8n's public webhook URL matches.&lt;/li&gt;
&lt;li&gt;Your Auth Token now lives in the workflow. That's fine for your own instance, but strip it before you share or export the workflow. On self-hosted n8n you can read it from an environment variable or n8n Variables instead of hardcoding it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get the workflow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workflow JSON (free, no credentials inside):&lt;/strong&gt; &lt;a href="https://smallbizautopilot.netlify.app/workflows/missed-call-text-back.n8n.json" rel="noopener noreferrer"&gt;https://smallbizautopilot.netlify.app/workflows/missed-call-text-back.n8n.json&lt;/a&gt;. Import it with &lt;em&gt;Workflows → … → Import from File&lt;/em&gt;, fill in the &lt;strong&gt;Settings&lt;/strong&gt; node, connect Twilio and Google Sheets, and follow the yellow sticky note.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain-English version&lt;/strong&gt; for non-developers, including Zapier and Make setups: &lt;a href="https://smallbizautopilot.netlify.app/missed-call-text-back-automation/" rel="noopener noreferrer"&gt;https://smallbizautopilot.netlify.app/missed-call-text-back-automation/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've seen how Twilio behaves when the caller hangs up during a &lt;code&gt;&amp;lt;Dial&amp;gt;&lt;/code&gt; with an &lt;code&gt;action&lt;/code&gt; URL, I'd like to hear about it in the comments. That's the one behaviour I'd most like more real-world reports on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write practical automation guides for small businesses at &lt;a href="https://smallbizautopilot.netlify.app/guides/" rel="noopener noreferrer"&gt;Small Business Autopilot&lt;/a&gt;. This post was drafted with AI assistance. The workflow was validated against current n8n node definitions, and the Twilio behaviour described is from Twilio's &lt;code&gt;&amp;lt;Dial&amp;gt;&lt;/code&gt; documentation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;— Jax, Small Business Autopilot&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>twilio</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
