Validate X-Twilio-Signature (HMAC-SHA1), add DLQ/replay, and never lose SMS status callbacks or voice events.
Works with webhooks from
Twilio signs webhook payloads with X-Twilio-Signature using HMAC-SHA1. Unlike most providers, Twilio's signature is computed over the full URL + sorted POST parameters, not just the body.
The algorithm: concatenate your webhook URL with all POST body parameters sorted alphabetically (key+value pairs with no separators), then HMAC-SHA1 with your Auth Token and base64-encode.
Server downtime and deploys shouldn't mean lost delivery receipts.
Visual debugging for X-Twilio-Signature with clear mismatch reasons: URL, param sorting, or secret issues.
Failed SMS status callbacks and voice events stored with full audit trail. One-click replay to staging or prod.
See exactly what happened, when. Response times, status codes, retry attempts—all visualized.
Add EventDock to your Twilio webhooks in under 5 minutes
Select Twilio as the provider and paste your upstream URL (your webhook handler for SMS/voice events).
Copy your Twilio Auth Token from the Twilio Console and paste it into EventDock as the signing secret.
In your Twilio Console, update your phone number's webhook URL or TwiML App to point to your EventDock ingest URL.
Send a test SMS, view it in EventDock's Stream, and enable alerts for failed deliveries.
Validate X-Twilio-Signature correctly (HMAC-SHA1 + URL + sorted params)
Twilio's signature is computed over: URL + sorted(key1+value1+key2+value2+...) then HMAC-SHA1 and base64 encoded.
const crypto = require('crypto');
function verifyTwilioSignature(url, params, signature, authToken) {
// Sort params alphabetically and concatenate key+value pairs
const sortedParams = Object.keys(params)
.sort()
.map(key => key + params[key])
.join('');
// Signature input: URL + sorted params (no separators)
const signatureInput = url + sortedParams;
// Compute HMAC-SHA1 and base64 encode
const expectedSignature = crypto
.createHmac('sha1', authToken)
.update(signatureInput)
.digest('base64');
// Timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Usage in Express
app.post('/twilio-webhook', express.urlencoded({ extended: false }), (req, res) => {
const signature = req.headers['x-twilio-signature'];
const fullUrl = 'https://your-domain.com/twilio-webhook';
const authToken = process.env.TWILIO_AUTH_TOKEN;
if (!verifyTwilioSignature(fullUrl, req.body, signature, authToken)) {
return res.status(401).send('Unauthorized');
}
// Process webhook...
res.status(200).send('OK');
});
import hmac
import hashlib
import base64
def verify_twilio_signature(url: str, params: dict, signature: str, auth_token: str) -> bool:
"""Verify Twilio X-Twilio-Signature (HMAC-SHA1, base64)."""
# Sort params alphabetically and concatenate key+value pairs
sorted_params = ''.join(
key + params[key]
for key in sorted(params.keys())
)
# Signature input: URL + sorted params
signature_input = url + sorted_params
# Compute HMAC-SHA1 and base64 encode
expected = base64.b64encode(
hmac.new(
auth_token.encode('utf-8'),
signature_input.encode('utf-8'),
hashlib.sha1
).digest()
).decode('utf-8')
# Constant-time comparison
return hmac.compare_digest(expected, signature)
# Usage in Flask
@app.route('/twilio-webhook', methods=['POST'])
def twilio_webhook():
signature = request.headers.get('X-Twilio-Signature')
full_url = request.url
auth_token = os.environ['TWILIO_AUTH_TOKEN']
if not verify_twilio_signature(full_url, request.form.to_dict(), signature, auth_token):
return 'Unauthorized', 401
# Process webhook...
return 'OK', 200
Twilio uses HMAC-SHA1 for signature verification. The signature is computed over the full webhook URL concatenated with all POST parameters sorted alphabetically (key+value pairs with no separators). The result is base64-encoded and sent in the X-Twilio-Signature header.
EventDock buffers the webhook and retries with exponential backoff. Failed events go to DLQ where you can replay them once your service is back online. No lost delivery receipts or status updates.
No. EventDock forwards original Twilio headers unchanged. We add X-EventDock-* metadata headers for tracing but preserve all original request headers and body.
Twilio's signature algorithm predates the widespread adoption of SHA-256 for webhooks. While SHA-1 has known weaknesses for collision attacks, HMAC-SHA1 remains secure for message authentication when used with a secret key.
Your Auth Token is available in the Twilio Console under Account > API Keys and Tokens. This is the secret used to verify X-Twilio-Signature. Keep it secure and never expose it in client-side code.
Common causes: (1) URL mismatch - the URL in your verification must match exactly what Twilio sends to, including protocol and trailing slashes. (2) Parameter sorting - parameters must be sorted alphabetically by key name. (3) URL encoding - decoded values should be used. (4) Using SHA-256 instead of SHA-1.
5,000 events/month free. No credit card required. 5-minute setup.
Start Free Trial