Field notes
How Twilio behaves, and where it bites
Form-encoded, not JSON, and a signature bound to the exact URL that was called.
How Twilio sends
Twilio POSTs application/x-www-form-urlencoded parameters, not JSON, and signs with X-Twilio-Signature computed over the full public URL plus the sorted parameters.
Where it bites
- A proxy in the path breaks verification. The signature covers the URL Twilio called. A rewrite, an added query parameter or a different host makes a perfectly genuine request fail.
- The body is form-encoded. Tooling that assumes JSON shows you nothing useful, and a parser that mangles it loses the delivery.
- There is no event name. What happened is spread across the parameters, so searching means searching the values.
The vault under it
Point a WebhookVault endpoint at Twilio and every delivery is stored in full: headers verbatim, bodies byte-for-byte, labelled with its parameters and searchable. Forward it onward to your own handler if you want the relay pattern, replay any delivery once the bug is fixed, and let a Watchdog notice the day it stops arriving.
Create an endpoint
Name it twilio-sms. Turn on forwarding to your own handler if you want the vault in front of it rather than beside it.
Register the capture URL
Twilio Console → the phone number or service → the webhook field for the event you want. Paste the capture URL.
Keep everything
Every delivery from here on is on the shelf: searchable, replayable, and watched for the day it goes quiet.
Verify from the record, not just at the door
Twilio's is a URL-bound scheme, so the vault verifies against the public capture URL as the sender saw it. The form parameters are stored and searchable like any other body.
The vault stores the signature headers verbatim with every request, so a delivery from last month can still be checked today. Verdicts are part of Solo and up. How verification works →
# headers, stored verbatim "X-Twilio-Signature": "0b1eV6…=" "Content-Type": "application/x-www-form-urlencoded" # body is form-encoded, not JSON MessageSid=SM2f81a4b9c0d34e5f& MessageStatus=delivered& To=%2B27825550101& From=%2B15005550006& AccountSid=AC7d2b…
Event catalogue
Twilio webhook events you'll see
Twilio names no event: the form fields of the request say what happened. These are the values you will meet most, and each capture is labelled with its parameters.
| Event | What it tells you |
|---|---|
| MessageStatus=delivered | A status callback: the carrier confirmed an outbound message was delivered. |
| MessageStatus=undelivered | The carrier did not deliver it; ErrorCode says why. |
| MessageStatus=failed | Twilio could not send the message; ErrorCode says why. |
| Body, From, To | An inbound message to one of your numbers. There is no status field; the parameters are the event. |
| CallStatus=completed | A voice call ended; CallDuration arrives in the same request. |
| CallStatus=no-answer | A call rang out without being answered. |
Questions
Twilio webhook questions, answered
The three that come up most when Twilio is wired to a new endpoint.
Why does my Twilio signature fail behind a proxy?
Twilio signs the full URL it called as well as the parameters. A proxy or redirect that changes the scheme, host, path or query string produces a different URL on your side, so the digest no longer matches even though the request is genuine. The vault verifies against the public capture URL exactly as Twilio saw it.
How do I verify X-Twilio-Signature?
Take the full URL, append each POST parameter name and value in alphabetical order of name, compute HMAC-SHA1 with your auth token, base64-encode it and compare it to the header in constant time. On Solo and up the vault does this at capture and puts the verdict on the row.
How do I find one message in the vault?
Twilio posts form-encoded parameters, not JSON, and the vault stores them in full. Search for the MessageSid or CallSid to find every callback for one message or call. Searching inside payload bodies starts on Pro.
Keep what Twilio actually posted.
Free tier, no card. Form bodies stored in full; search inside them starts on Pro.
Sign up with GitHub, Google or email. Two-factor authentication is required on every account, Free included, for account security.