Home / Blog / Build a missed-call text-back in n8n and Twilio, with Jobber requests
Build a missed-call text-back in n8n and Twilio, with Jobber requests
A step-by-step n8n + Twilio missed-call text-back: catch unanswered calls, text back, book a Jobber request and page on-call for emergencies.
You already run n8n for something, maybe invoices or a review request. Now you want the same box to catch the calls your techs can't pick up from under a sink, text those callers back, and get the job into Jobber without a monthly subscription to another "AI receptionist."
The n8n templates you'll find for this are a good start. One logs missed calls to Google Sheets and emails a reply. Another texts the caller a booking link and alerts the owner. Neither one writes to Jobber, has a rule for gas leaks or burst pipes, or comes with test results. This post walks through the build we published as the missed-call text-back recipe: which Twilio setting catches the missed call, how n8n keeps track of the conversation, how the request lands in Jobber, and the traps we found in the docs.
If you're still deciding whether text-back is worth it at all, start with what it costs and the cheaper fixes to try first and whether texting a missed caller is legal. This post assumes you've decided to build.
Before you build: what you need
- A Twilio number with A2P 10DLC registration. US carriers filter business texts from numbers that aren't registered. Plan for the brand and campaign review before launch. Our legal post covers what registration involves.
- Your business line routed through Twilio. Twilio can only see a missed call if the call passes through it. Usually that means you port the main number to Twilio or forward it there, and Twilio rings your office or cell.
- n8n reachable from the internet over HTTPS, either n8n Cloud or self-hosted behind a domain. Our testbed ran n8n 2.45.4.
- A Jobber developer app with permission to create clients and requests. If you don't use Jobber, the texting part still works. You'd swap out the two Jobber nodes.
- An AI model API key for reading the customer's replies. A small model is enough. Its only job is to pull fields like the address and the problem out of the text.
Step 1: Catch the missed call in Twilio
When a call comes in, Twilio runs your TwiML. The <Dial> verb rings your real phone. Two settings matter.
timeout. This is how long Twilio rings before it gives up. Twilio's docs say the default is 30 seconds and that Twilio adds a five-second buffer. Set it shorter than the time your cell or desk phone takes to send a call to its own voicemail. Here's why that matters. Twilio's DialCallStatus reports completed when "the called party answered the call." In our reading, a phone's voicemail picking up looks the same as a person answering. If your voicemail answers first, Twilio sees an answered call and no text goes out. Test this with your own phone.
Where Twilio reports the outcome. You have two options.
- The
<Dial action="...">URL. Twilio postsDialCallStatusthere when the dial ends:busy,no-answer,failedorcanceledfor a miss. Twilio then expects TwiML back, because the caller may still be on the line. Our workflow's webhook answers immediately with an empty TwiML document,<Response></Response>, sent astext/xml. That is valid TwiML, and it ends the call. If you use this option and want callers to reach voicemail, change that response to a<Record>or a short<Say>. - A
statusCallbackon the<Number>you dial. Twilio posts the dialed leg'sCallStatusthere, includingbusy,failedandno-answer. Twilio doesn't need a TwiML answer from a status callback, so this fits our workflow as shipped. Point your<Dial action>at a simple voicemail instead.
A minimal version of option 2 looks like this. The URLs and numbers are placeholders:
<Response>
<Dial timeout="20" action="https://example.com/your-voicemail-twiml">
<Number statusCallbackEvent="completed"
statusCallback="https://your-n8n.example.com/webhook/mss-r1-call-status">+15555550123</Number>
</Dial>
</Response>
The workflow reads DialCallStatus if it's present and falls back to CallStatus, so it accepts either option. One case we couldn't confirm from Twilio's docs is a caller who hangs up while it's still ringing. The docs say the session ends. Test it before you rely on it.
Step 2: Decide whether to text
The first n8n Code node is where most of the judgment sits. It does four things, in order:
- Was it a miss? Only
no-answer,busy,failedorcanceledcontinue. An answered call stops here (scenario S1 in our test log). - Can we text this number? The caller ID must look like a US or Canadian mobile number (
+1and ten digits). Blocked, anonymous and international callers are skipped (S7), and so is anyone who has opted out. - Have we already texted them? One text per caller per 12 hours, so a customer who calls three times in a row gets one text, not three (S6).
- Office open or closed? The default hours are Mon–Fri 7:30–17:30 and Sat 8–12 in your
BUSINESS_TZ. During open hours the text says "we'll call you back." After hours it says you can book by text, and it includes a one-line gas-leak warning. Both end with "Reply STOP to opt out."
One honest cost: the after-hours text runs to two SMS segments because of that safety line, so each one costs twice as much as a one-segment text. We left the line in on purpose.
Step 3: Keep track of the conversation
The customer's replies arrive at a second webhook (mss-r1-sms). The workflow keeps each conversation, the 12-hour memory and the opt-out list in n8n's workflow static data.
Here is the trap. n8n's docs say "static data isn't available when testing workflows. The workflow must be published and called by a trigger or webhook to save." If you test with the editor's "listen for test event" URL, the workflow forgets every message between runs. The bot then asks the same question twice and you'll assume it's broken. Activate the workflow and point Twilio at the production webhook URL (/webhook/..., not /webhook-test/...). If you need memory that survives re-imports, n8n suggests data tables instead.
Each reply goes to the AI model with a strict prompt. The prompt asks for JSON fields (name, service address, issue summary, trade, urgency, preferred time, existing customer or not), and nothing else.
Step 4: Don't let the model decide what's an emergency
The model suggests an urgency, and plain code checks it afterward. Three regular-expression checks run on the customer's own words, and the model can't overrule them:
- Safety words (gas smell, rotten egg, carbon monoxide, CO alarm): the customer gets "leave the building and call your gas utility's emergency line or 911 from outside." The on-call tech is paged and no booking happens (S5).
- Emergency words (burst pipe, flooding, sewage backing up, no water at all, no heat plus freezing): the request is marked emergency and on-call gets a text with the Jobber link (S4).
- System-down words (no heat, no AC, no hot water): the request is marked at least urgent.
We chose this split because a small model misreads urgency now and then, and a missed gas leak is the one mistake you can't fix later. Our after-hours triage post covers how to choose your own emergency rules.
Step 5: Create the Jobber request
Once the conversation has a name, address and problem, the workflow calls Jobber's GraphQL API twice. clientCreate creates the client, then requestCreate creates the request. The title gets an [EMERGENCY] or [URGENT] prefix where it applies, and the details note that it came from the after-hours text-back (S3). Three details from Jobber's developer docs will trip you up if you skip them:
- Access tokens expire after 60 minutes. Jobber's docs say so plainly. Our workflow's two Jobber nodes use an n8n "OAuth2 API" credential (authorize URL
https://api.getjobber.com/api/oauth/authorize, token URLhttps://api.getjobber.com/api/oauth/token), so n8n refreshes the token for you. A pasted token would stop the bookings an hour later. - Refresh tokens rotate. For apps created after January 2, 2024, each refresh returns a new refresh token, and using the old one fails. n8n's credential stores the new token. In our mock test, n8n refreshed an expired token and saved the rotated refresh token. Don't copy tokens by hand.
- The version header is required. Every request must send
X-JOBBER-GRAPHQL-VERSION. Jobber supports old versions for at least 12 months and keeps them reachable for up to 18. Our workflow pins2026-09-25, the newest version on Jobber's changelog when we checked on October 28, 2026. Before you go live, make sure Jobber still lists it, and check the response'sextensionsfor deprecation warnings.
We also took the requestCreate input fields from a community copy of Jobber's schema, not from a live account. Run one mutation in Jobber's GraphiQL explorer before you trust the node.
Step 6: Lock it down
- Check Twilio's signature. Twilio signs every webhook with an
X-Twilio-Signatureheader. It's an HMAC-SHA1 of your webhook URL and the request parameters, keyed with your auth token. Since October 28, 2026 our workflow checks it on both webhooks and ignores requests with a missing or wrong signature, so someone who finds the URL can't trigger texts. Two settings make it work:TWILIO_AUTH_TOKEN, andPUBLIC_WEBHOOK_BASEset to the exact public URL Twilio calls. If a proxy changes the host or scheme, valid signatures will fail, and the workflow will drop real calls. Test one call after any proxy change. - One SMS webhook per number. Twilio sends a number's incoming texts to one URL. If the same number also runs quote follow-up, put a small router in front of both workflows.
- Hand off after booking. Once a request exists, further texts from that customer are forwarded to
OFFICE_PHONE(S8). The bot doesn't keep chatting. - Opt-outs. Carrier keywords like STOP are handled by Twilio, and the workflow also records them (S11). Plain-English requests like "please stop texting me" are caught by a phrase list (S9). "The leak won't stop" is not treated as an opt-out (S10). The phrase list will miss some wordings.
What we tested, and what we didn't
The recipe is verified with mocks. On October 27, 2026 it passed 11 of 11 scenarios on n8n 2.45.4. Twilio, Jobber and Stripe were local mocks built from their docs, and the AI was a small local model (Qwen2.5-3B-Instruct, 4-bit). It has not run against a live Twilio number, a real carrier or a live Jobber account. The hosted model we suggest in .env.example is untested. On October 28, 2026 we added mock tests for the signature check (forged and unsigned requests ignored), the TwiML reply and the Jobber token refresh; all passed. Before you rely on it, call your own number after hours and walk through a no-heat call, a burst pipe and a gas smell, then check what lands in Jobber and on the on-call phone.
There's no Housecall Pro or ServiceTitan version yet. The HVAC and plumbing hub lists everything else we've built for shops like yours. You can download the workflow, .env.example and test log from the recipe page.
Sources
All checked October 27, 2026.
- Twilio Docs, TwiML
<Dial>:action,DialCallStatusvalues,timeoutdefault and buffer. Vendor documentation. - Twilio Docs, TwiML
<Number>:statusCallbackand theCallStatusvalues it reports. Vendor documentation. - Jobber Developer Center, API changelog: current API versions, checked October 28, 2026. Vendor documentation.
- Twilio Docs, Webhooks security:
X-Twilio-Signature, HMAC-SHA1. Vendor documentation. - n8n Docs,
getWorkflowStaticDataand Webhook node: static data not saved in test runs; test vs production URLs. Vendor documentation. - Jobber Developer Center: App authorization (60-minute access tokens), Refresh token rotation, API versioning. Vendor documentation.
- n8n workflow templates 18233 and 21153. Community templates, described from their public pages.
- Our test log and workflow: missed-call text-back recipe. Mock-tested only, as described above.
Found an error or a better source? Corrections policy. More articles on the blog.