What the signature covers
Twilio attaches an X-Twilio-Signature header to the requests it sends your app. The header is an HMAC-SHA1 hash keyed with the account's auth token, computed over the full webhook URL as configured in Twilio plus the request parameters, sorted alphabetically and appended for form posts. Because the URL is part of the input, your validator must be handed the same URL string that Twilio used, including any query string and percent-encoding.
- Pass the whole URL to the validator, query string included.
- Do not decode or re-encode it; the documentation says doing so fails validation.
- Twilio advises using its SDK validator instead of writing your own.
Why genuine callbacks fail behind a proxy
A load balancer, tunnel or reverse proxy often changes what your application sees: the scheme may arrive as http while Twilio called https, the host or port may differ, a path prefix may be stripped. If your code builds the URL from the incoming request, it may not equal the URL Twilio signed. Twilio's page does not discuss proxies directly, so this is a deduction from how the signature is built, but it is the most common reason a correct secret still fails.
- Log, in staging only, the URL your code passes to the validator next to the one configured in Twilio.
- Prefer configuring the public URL explicitly rather than rebuilding it per request.
- For JSON requests use the SDK method that takes the raw body, which also checks the body hash.
What test credentials can and cannot show
Twilio's test credentials let you call the messaging endpoint without sending a real text or being billed, and documented magic numbers trigger specific errors. For example, a To number of +15005550001 returns an invalid-number error with code 21211. The documentation also says messages sent with test credentials do not trigger status callbacks. That makes them right for testing how your app handles a send error and wrong for testing delivery tracking.
- Use test credentials for the error paths of the send function.
- Use a live send to a phone you own, on staging, for the callback path.
- Forged and out-of-order callbacks can be tested without Twilio by posting signed test requests.
A short test plan
Create three groups of tests. Send errors: use the documented magic numbers with test credentials and check the app records one failed attempt and does not loop. Callback handling: post signed requests, generated with your validator's inverse in a test, for each status in a mixed order and assert the final state, then post one with a wrong signature and assert nothing changes. Live path: send one message to your own phone from staging and watch the stored history.
How the paid outcome is accepted
The Twilio outcome for one notification type includes exactly these checks, with the documented invalid-number test value and a live send to your own phone. The fixed £345 price is untested and payment follows your sign-off. You hold the auth token; it is never part of the first enquiry.
Sources and limits
- Twilio: webhook security Checked 2026-10-11.
- The X-Twilio-Signature header is an HMAC-SHA1 over the full webhook URL and the sorted POST parameters, keyed by the account auth token.
- Decoding or re-encoding the URL breaks validation; Twilio says not to implement your own validation and to use its SDK.
- JSON bodies add a bodySHA256 query parameter that the SDK checks.
- Twilio: test credentials Checked 2026-10-11.
- Messages sent with test credentials do not trigger status callbacks.
- Documented magic numbers produce specific errors, for example an invalid To number returning error 21211.