Replies and WhatsApp
Reply as the contact, see your app's reply webhook and TwiML answers, and use WhatsApp and media.
Try it in the demo.
What it solves
- Your app answers incoming SMS (STOP, YES, a support chat), but you cannot text your dev machine.
- Your webhook verifies
X-Twilio-Signatureand answers with TwiML; you want to see both sides. - Your Twilio number has a fallback URL; you want to see it used when the main URL fails.
Reply as the contact
In a conversation, type in Reply as recipient and press Send reply (or Enter). Attach files adds images or files, which arrive as MMS or WhatsApp media. The message is stored as incoming and fires sms.inbound.
Where the reply goes
Like a real provider, SendMock looks at the number the contact wrote to (set up in Numbers):
- If the number belongs to a Messaging Service whose inbound handling is "Send a webhook", the service's request URL is called.
- Otherwise the number's inbound URL is called, in the format set on the number (Twilio, Vonage, Plivo, ... or native JSON), with
X-Twilio-Signaturefor Twilio. - If that call fails, the fallback URL is called with the same request plus
ErrorCodeandErrorUrl, as Twilio does. Nothing is retried after that; use Resend reply webhook once your handler is fixed. - A number without an inbound URL sends the message to rules only (Webhooks > Rules on
sms.inbound). With both set, both receive it.
What your app receives
In Twilio's format (the default), the same "A message comes in" request Twilio sends, so the handler you run in production works unchanged:
POSTasapplication/x-www-form-urlencoded, orGETwith the fields in the query string when the number's method isGET. A Messaging Service always usesPOST.- Fields:
MessageSid,SmsSid,SmsMessageSid,AccountSid,MessagingServiceSid(when the number is in a service),From,To,Body,NumMedia,NumSegments,SmsStatus,ApiVersion, plusMediaUrl{N}andMediaContentType{N}for each file (links to SendMock's copy) andProfileNameandWaIdon WhatsApp. - Headers:
X-Twilio-Signature(signed with the auth token in Settings > SMS providers > Twilio),I-Twilio-Idempotency-TokenandUser-Agent: TwilioProxy/1.1.
What your app can answer
Answer with TwiML as text/xml, application/xml or text/html, as Twilio reads them. A text/plain answer is sent back to the contact as the reply text.
<Response>
<Message statusCallback="/sms/status">Thanks, your booking is confirmed.</Message>
</Response>| TwiML | What happens |
|---|---|
<Message> with text, or <Body> and <Media> | a reply in the same conversation, from the number that was texted to the contact, delivered like any sent message |
to and from | send the reply to or from another number |
statusCallback | status callbacks for the reply, in Twilio's format |
action (and method) | called once the reply is sent, with MessageStatus; its TwiML replaces the verbs left in the document |
<Redirect> (and method) | fetches TwiML from another URL; the verbs after it are skipped |
an empty <Response/>, an empty body or 204 | no reply |
Several <Message> verbs send several replies, in order. URLs can be relative to the URL that answered. One incoming message makes at most 10 TwiML requests, redirects and actions included, so two handlers that redirect to each other stop.
When the call fails
Failures carry Twilio's error codes, shown on the message and in Message details > Reply webhook:
| What happened | Code |
|---|---|
an answer other than 2xx, or none within 15 seconds | 11200 HTTP retrieval failure |
| the connection was refused or reset | 11205 HTTP connection failure |
the body is not a TwiML <Response> document | 12100 Document parse failure |
| any other content type, such as JSON | 12300 Invalid Content-Type |
The result, on screen
- Under the incoming bubble: "Reply webhook 200", "Reply webhook 200, 1 TwiML reply", "Reply webhook failed: 11205 HTTP connection failure", "Fallback 200 after 11205", or "Rules only".
- If your app answered with TwiML
<Message>, the answer appears in the conversation with a TwiML reply badge that jumps to the message it answers. - Message details > Reply webhook shows each attempt (primary or fallback), the request with its form fields and headers (signature included), the response or the error with its Twilio code, the parsed TwiML verbs, and Resend reply webhook.
WhatsApp and media
- Send to or from
whatsapp:+8801700000000through the Twilio API and the conversation shows the WhatsApp badge and styling. - Media (
MediaUrlon Twilio,media_urlsor inlinemediaon the native API) shows as thumbnails and file chips in the bubble, with an MMS or Media badge and a media list in Message details. - Simulate read on an outgoing message fires
sms.openedand turns the ticks blue, for read receipts. Only outgoing messages can be read: opening a message your app received answers409.
WhatsApp Cloud API
Apps that call Meta's WhatsApp Business Cloud API directly send to SendMock the same way: change the host https://graph.facebook.com to the provider port, http://127.0.0.1:8026 (or http://127.0.0.1:8025/providers/whatsapp_cloud), and keep the path.
-
Send as your app does, for example with
curl:curl -X POST http://127.0.0.1:8026/v21.0/106540352242922/messages -H "Authorization: Bearer any" -H "Content-Type: application/json" -d '{"messaging_product":"whatsapp","to":"16505551234","type":"text","text":{"body":"Your order shipped"}}' -
The answer is Meta's:
messaging_product,contactswith thewa_id, andmessageswith awamid.id (message_status: "accepted"for a template). -
The message shows in SMS as a WhatsApp conversation from the provider's business number (
+15550783881unless you change the phone_number credential in SMS > Providers > WhatsApp Cloud).
Every message type is stored with a body you can read: text, templates (Template order_update (en_US) and their body parameters), images, video, audio, documents and stickers (their caption, with media links downloaded as the file), locations, contacts, interactive buttons and lists, and reactions. A request Meta would refuse (no messaging_product, no to, an unknown type, a missing text.body) gets Graph's error object with code 100 and nothing is stored. { "messaging_product": "whatsapp", "status": "read", "message_id": "wamid..." } marks the conversation read and answers { "success": true }. The magic numbers work here too: a refused number answers code 131026, "Message undeliverable".
Media works as on Meta: upload a file with POST /v21.0/<phone_number_id>/media (multipart, with messaging_product=whatsapp, type and file) and get its id; GET /v21.0/<media_id> answers its url, mime_type, sha256 and file_size; the url downloads the file; DELETE /v21.0/<media_id> removes it. Send an image, video, audio file, document or sticker by that id (or by a link), and the message carries the file. Uploaded files are kept like messages: pruned with them, cleared by a reset, and deleted with their project.
Webhooks to your app
Meta sends one app callback URL every status and every reply. Set it in SMS > Providers > WhatsApp Cloud, as the webhook_url credential, and SendMock does the same:
- Set webhook_url to your handler, for example
http://127.0.0.1:3000/whatsapp/webhook, and save. - On the Callbacks tab, press Verify callback URL. SendMock calls your URL with
GET ?hub.mode=subscribe&hub.verify_token=<verify_token>&hub.challenge=<number>, as Meta does, and says whether your handler answered the challenge. - Send a message. Each status (
sent,delivered,read, andfailedwith error131026for an undeliverable number) is posted to your URL in Meta'swhatsapp_business_accountenvelope, signed withX-Hub-Signature-256: sha256=<HMAC-SHA256 of the body with the app_secret credential>. They are retried and listed on the message like any status callback. - Reply as the customer in the conversation (to the business number,
+15550783881by default). The reply is posted to the same URL as amessageschange, signed the same way.
The envelope uses the phone_number_id and business_account_id credentials for metadata.phone_number_id and the entry id. The same format is the whatsapp.cloud template in Webhooks > Templates, for rules that send other events in Meta's shape (they read the app secret from the whatsapp_cloud_app_secret variable).
Step by step: Recipe: two-way SMS.