← Back to Blog
Guide twilio webhooks security signature sms

Validating Twilio Webhook Signatures: A Complete Guide

Twilio signs every webhook with a scheme that's different from Stripe and GitHub — it includes the URL and sorted POST parameters. Here's how X-Twilio-Signature works and how to validate it correctly.

JW

Jason Warner

June 9, 2026

Validating Twilio Webhook Signatures: A Complete Guide

If you've validated Stripe or GitHub webhooks before, you might assume Twilio works the same way. It doesn't. Twilio's signature scheme is genuinely different, and that difference is why so many Twilio integrations either skip validation entirely (bad) or implement it incorrectly (worse).

How X-Twilio-Signature Is Different

Stripe and GitHub sign the raw request body. Twilio signs something more elaborate: the full request URL concatenated with the sorted POST parameters.

The algorithm Twilio uses is:

  1. Take the full URL of your webhook endpoint, exactly as Twilio called it (including query string).
  2. If the request is a form-encoded POST, sort the POST parameters alphabetically by key.
  3. Append each key immediately followed by its value to the URL string — no separators.
  4. Compute HMAC-SHA1 of that string using your Twilio auth token as the key.
  5. Base64-encode the result.

That value should match the X-Twilio-Signature header.

signature = base64(HMAC-SHA1(url + sorted_concatenated_params, auth_token))

Two things jump out immediately: it's SHA1, not SHA256, and it uses your auth token, not a per-endpoint signing secret. Both are different from Stripe.

A Working Implementation

public bool ValidateTwilioSignature(
    string authToken, string signatureHeader,
    string fullUrl, IDictionary<string, string> postParams)
{
    // 1. Sort params by key, concatenate key+value onto the URL
    var sb = new StringBuilder(fullUrl);
    foreach (var kvp in postParams.OrderBy(p => p.Key, StringComparer.Ordinal))
    {
        sb.Append(kvp.Key);
        sb.Append(kvp.Value);
    }

    // 2. HMAC-SHA1 with the auth token, base64-encoded
    using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(authToken));
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()));
    var expected = Convert.ToBase64String(hash);

    // 3. Constant-time comparison
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected),
        Encoding.UTF8.GetBytes(signatureHeader));
}

The Gotchas That Break Validation

The URL has to match exactly

This is where most people get stuck. The URL you reconstruct on your server has to be byte-for-byte the URL Twilio used. If Twilio called https://example.com/sms but your server is behind a proxy that reports http://internal-host:8080/sms, the strings differ and validation fails.

Pay attention to:

  • Scheme: https vs http — a TLS-terminating load balancer often makes your app think the request was http.
  • Host: the public hostname vs an internal one.
  • Query string: if Twilio's request URL had query parameters, they must be included, and they are not part of the sorted POST params.

HTTPS vs HTTP and the X-Forwarded headers

Behind a reverse proxy, reconstruct the original URL from X-Forwarded-Proto and X-Forwarded-Host rather than the local request. Getting this wrong is the number-one cause of "it works in the Twilio console test but fails in production."

JSON webhooks sign differently

If you've configured Twilio to send JSON instead of form-encoded data, the parameter-sorting step doesn't apply — Twilio signs differently. Most Twilio webhooks are form-encoded, but check your configuration.

Why This Is Worth Offloading

Twilio's scheme is unforgiving precisely because it depends on the exact URL, which is the part of the request most likely to be rewritten by your infrastructure. Every proxy hop is a chance to break validation.

Bluejay Relay handles X-Twilio-Signature validation at the edge, using the original inbound URL before any proxying or rewriting happens. You paste in your Twilio auth token once, and Bluejay Relay validates every incoming request, rejects anything that doesn't match, and forwards the verified webhook to your application. You get the same one-time-setup experience for Twilio that you do for Stripe, GitHub, and Shopify — even though all three sign their payloads completely differently.


Building on Twilio? Try Bluejay Relay free — Twilio signature validation is built in, so you can focus on handling the message instead of debugging HMAC.

#twilio #webhooks #security #signature #sms

Verify every webhook signature.

HMAC, Stripe, GitHub, and Twilio signature verification, IP allowlisting, and PII scrubbing — built in, no custom code. Free to start.