X-Twilio-Signature. For form-encoded webhooks it signs the request URL followed by the sorted form fields; for JSON bodies it signs the URL, which carries a hash of that body. Register the auth token and webhook.co handles both modes.
Get the auth token
In the Twilio Console, open Settings → Account settings → API keys & auth tokens and copy the Auth Token. It must be the primary token: Twilio lets you create a secondary token ahead of a rotation, but webhooks keep being signed with the primary until you promote it. If you use a subaccount, use that subaccount’s token. An API key is not a substitute — Twilio names the auth token as the signing key everywhere it describes this scheme.Register it on your endpoint
How the signature is checked
Twilio has two constructions, and which one applies is decided by whether abodySHA256 query parameter is present.
- Form requests — the full request URL, then every form field sorted by name, each appended as name immediately followed by value with no delimiter anywhere. Sorting is byte order, so
CallSidprecedesCaller. - JSON requests — the signature covers the URL alone. The body is bound in indirectly: Twilio appends
bodySHA256, a hex SHA-256 of the raw body, to the URL it signs. webhook.co checks both halves — the signature over the URL, and that the hash of the captured body equals thebodySHA256in it. - Algorithm — HMAC-SHA1, base64-encoded. The auth token is the key, used verbatim and case-sensitively.
The URL is the hard part
Everything difficult about Twilio verification is URL reconstruction. The signed string starts with the URL Twilio dialled, not the one your server sees behind a proxy, and a single byte of difference fails the check. Twilio’s own SDKs concede the ambiguity: they compute the signature against several candidate URLs because the default port is not consistently present in what the backend signed. For JSON deliveries webhook.co mirrors that, trying the URL as captured and again with the default port toggled; form deliveries are checked against the URL exactly as captured. Either way it cannot repair a genuine mismatch, so the webhook URL configured in Twilio must be the endpoint’s ingest URL exactly — same host, same path, same query, with percent-escapes left encoded. Two more from Twilio’s documentation worth knowing: any username and password embedded in the URL are stripped before signing, and voice callbacks over HTTPS drop the port while SMS callbacks keep it.Form field names are sorted and folded to one value each before the signed string is built, so a
body repeating a name is checked against that name’s first value.
No replay window
Twilio’s signed material is the URL and the parameters — no timestamp, no nonce, no expiry. A captured request and signature stay valid until the auth token is rotated, so webhook.co enforces no time tolerance here. Deduplicate on the identifier in the payload, such asMessageSid or CallSid.