Provider guide

Twilio webhooks, kept properly.

Twilio's signature covers the URL as well as the parameters, which is unusual and matters more than it sounds. Anything that rewrites the URL between Twilio and your handler invalidates the signature without touching the payload.

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.

01

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.

02

Register the capture URL

Twilio Console → the phone number or service → the webhook field for the event you want. Paste the capture URL.

03

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 →

captured · form-encoded✓ verified
# 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.

EventWhat it tells you
MessageStatus=deliveredA status callback: the carrier confirmed an outbound message was delivered.
MessageStatus=undeliveredThe carrier did not deliver it; ErrorCode says why.
MessageStatus=failedTwilio could not send the message; ErrorCode says why.
Body, From, ToAn inbound message to one of your numbers. There is no status field; the parameters are the event.
CallStatus=completedA voice call ended; CallDuration arrives in the same request.
CallStatus=no-answerA 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.