Twilio Webhook Specs, Events, and Examples
Twilio uses webhooks to notify third-party apps of events in its main products including voice calls initiated, SMS messages sent, and dialogs started in autopilot. You can wire Twilio events to trigger action in third-party apps such as record when someone responds to an SMS message.
Unlike most providers, Twilio doesn't sign the raw request body. It signs the full URL Twilio requested plus the POST parameters, which means the URL your code reconstructs must match the one Twilio called exactly — including scheme, host, port, and query string.
Specifications |
|
Supported Events |
|
Security Headers |
|
Documentation | |
SDKs and Sample Code |
|
Sample Validation
Twilio recommends its helper libraries for validation. For a form-encoded POST, the manual equivalent is:
const crypto = require('crypto')
const express = require('express')
const app = express()
const sigHeader = 'X-Twilio-Signature'
const hashAlgo = 'sha1'
const encode = 'base64'
const authToken = process.env.TWILIO_AUTH_TOKEN
// The exact URL configured in Twilio. Behind a proxy or load balancer,
// don't rebuild it from request headers — they may not match.
const webhookUrl = 'https://example.com/twilio-webhook'
app.post('/twilio-webhook', express.urlencoded({ extended: false }), (req, res) => {
//01: Build the signed string: URL + sorted param names and values
const params = req.body
const message = Object.keys(params).sort()
.reduce((acc, key) => acc + key + params[key], webhookUrl)
//02: Validate signature
const digest = Buffer.from(crypto.createHmac(hashAlgo, authToken)
.update(Buffer.from(message, 'utf8')).digest(encode), 'utf8')
const providerSig = Buffer.from(req.get(sigHeader) || '', 'utf8')
if (providerSig.length !== digest.length || !crypto.timingSafeEqual(digest, providerSig)) {
return res.status(403).send('Request unauthorized')
}
//03: Process message
res.type('text/xml').send('<Response></Response>')
})