Twilio Webhook Integration
Twilio webhooks enable real-time notifications for SMS messages, voice calls, WhatsApp messages, and other communication events. Configure webhooks to receive updates about message delivery, call status, and user interactions.
Quick Start
- Get your Unhook URL:
https://unhook.sh/wh_YOUR_ID
- Configure in Twilio Console: console.twilio.com
- Start receiving events locally:
unhook listen
Configuration Steps
1. Access Twilio Console
Navigate to the Twilio Console:
Twilio webhooks are configured per service and resource:
SMS/MMS Webhooks
- Navigate to Phone Numbers → Manage → Active Numbers
- Click on the phone number you want to configure
- In the Messaging section:
- Webhook URL for incoming messages:
https://unhook.sh/wh_YOUR_ID?type=sms
- Status callback URL:
https://unhook.sh/wh_YOUR_ID?type=sms-status
- Select HTTP POST as the method
- Click Save
Voice Call Webhooks
- In the same phone number configuration
- In the Voice & Fax section:
- Webhook URL for incoming calls:
https://unhook.sh/wh_YOUR_ID?type=voice
- Status callback URL:
https://unhook.sh/wh_YOUR_ID?type=call-status
- Select HTTP POST as the method
- Click Save
WhatsApp Webhooks
- Navigate to Messaging → Services
- Select your WhatsApp sender
- Configure webhooks:
- Incoming Message URL:
https://unhook.sh/wh_YOUR_ID?type=whatsapp
- Status Callback URL:
https://unhook.sh/wh_YOUR_ID?type=whatsapp-status
Programmable Voice (SIP)
- Navigate to Voice → Manage → SIP Domains
- Click on your SIP domain
- Set the Voice URL:
https://unhook.sh/wh_YOUR_ID?type=sip
3. Event Types
SMS/MMS Events
Incoming Message Parameters:
MessageSid
- Unique message identifier
From
- Sender’s phone number
To
- Recipient’s phone number
Body
- Message content
NumMedia
- Number of media attachments
MediaUrl{N}
- URLs for media attachments
FromCity
, FromState
, FromCountry
- Sender location
Status Callback Events:
sent
- Message sent to carrier
delivered
- Message delivered to device
undelivered
- Message failed to deliver
failed
- Message could not be sent
receiving
- Inbound message received
received
- Inbound message processed
Voice Call Events
Incoming Call Parameters:
CallSid
- Unique call identifier
From
- Caller’s phone number
To
- Called phone number
CallStatus
- Current call status
Direction
- inbound/outbound-api/outbound-dial
CallerName
- Caller ID name (if available)
Call Status Events:
initiated
- Call started
ringing
- Phone is ringing
answered
- Call answered
completed
- Call ended
busy
- Busy signal
failed
- Call failed
no-answer
- No answer
WhatsApp Events
Similar to SMS with additional parameters:
ProfileName
- WhatsApp profile name
WaId
- WhatsApp ID
ButtonText
- Text of button pressed (interactive messages)
ButtonPayload
- Payload of button pressed
Testing Webhooks
Using Twilio Test Credentials
- Find test credentials in Console → Account → API keys & tokens
- Use test phone numbers:
PHONE_NUMBER
- Valid number for testing
PHONE_NUMBER
- Invalid number (triggers error)
Send Test Messages
# Send test SMS using Twilio CLI
twilio api:core:messages:create \
--from "PHONE_NUMBER" \
--to "PHONE_NUMBER" \
--body "Test message"
# Make test call
twilio api:core:calls:create \
--from "PHONE_NUMBER" \
--to "PHONE_NUMBER" \
--url "http://demo.twilio.com/docs/voice.xml"
Request Examples
Incoming SMS
{
"MessageSid": "MESSAGE_SID",
"AccountSid": "ACCOUNT_SID",
"MessagingServiceSid": "MESSAGING_SERVICE_SID",
"From": "PHONE_NUMBER",
"To": "PHONE_NUMBER",
"Body": "Hello from Twilio!",
"NumMedia": "0",
"FromCity": "SAN FRANCISCO",
"FromState": "CA",
"FromCountry": "US",
"FromZip": "94105",
"ToCity": "NEW YORK",
"ToState": "NY",
"ToCountry": "US",
"ToZip": "10001"
}
SMS Status Callback
{
"MessageSid": "MESSAGE_SID",
"MessageStatus": "delivered",
"To": "PHONE_NUMBER",
"From": "PHONE_NUMBER",
"ApiVersion": "2010-04-01",
"AccountSid": "ACCOUNT_SID",
"ErrorCode": null,
"ErrorMessage": null
}
Incoming Voice Call
{
"CallSid": "CALL_SID",
"AccountSid": "ACCOUNT_SID",
"From": "PHONE_NUMBER",
"To": "PHONE_NUMBER",
"CallStatus": "ringing",
"Direction": "inbound",
"CallerName": "John Doe",
"FromCity": "SAN FRANCISCO",
"FromState": "CA",
"FromCountry": "US",
"FromZip": "94105"
}
Webhook Security
Validate Requests with X-Twilio-Signature
Twilio signs all webhook requests. Verify them:
const twilio = require('twilio');
function validateTwilioWebhook(req, res, next) {
const twilioSignature = req.headers['x-twilio-signature'];
const url = `https://unhook.sh${req.originalUrl}`;
const params = req.body;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const isValid = twilio.validateRequest(
authToken,
twilioSignature,
url,
params
);
if (!isValid) {
return res.status(403).send('Forbidden');
}
next();
}
IP Allowlisting (Optional)
For additional security, allowlist Twilio’s IP ranges:
TwiML Responses
For voice and messaging webhooks, respond with TwiML:
<!-- SMS Auto-reply -->
<Response>
<Message>Thanks for your message!</Message>
</Response>
<!-- Voice Response -->
<Response>
<Say>Hello! Thanks for calling.</Say>
<Play>https://example.com/hold-music.mp3</Play>
</Response>
Status Callbacks
For status callbacks, return HTTP 200 OK:
app.post('/webhook/status', (req, res) => {
// Process the status update
console.log(`Message ${req.body.MessageSid} status: ${req.body.MessageStatus}`);
// Acknowledge receipt
res.status(200).send('OK');
});
Best Practices
1. Handle Retries
Twilio retries failed webhooks with exponential backoff:
const processedEvents = new Map();
function handleWebhook(req, res) {
const eventId = req.body.MessageSid || req.body.CallSid;
if (processedEvents.has(eventId)) {
console.log(`Event ${eventId} already processed`);
return res.status(200).send('OK');
}
processedEvents.set(eventId, Date.now());
// Process the webhook...
}
2. Respond Quickly
Twilio expects a response within 15 seconds:
app.post('/webhook', async (req, res) => {
// Send response immediately
res.status(200).send('<Response></Response>');
// Process asynchronously
setImmediate(() => {
processWebhookAsync(req.body);
});
});
3. Error Handling
Implement proper error handling:
app.post('/webhook', (req, res) => {
try {
// Process webhook
const response = processIncomingMessage(req.body);
res.type('text/xml').send(response);
} catch (error) {
console.error('Webhook error:', error);
// Return empty TwiML to prevent Twilio errors
res.type('text/xml').send('<Response></Response>');
}
});
4. Use Appropriate Status Codes
200 OK
- Successfully processed
204 No Content
- Acknowledged, no response needed
400 Bad Request
- Invalid request
403 Forbidden
- Failed authentication
Common Issues
Webhook URL Not Reachable
- Ensure your Unhook tunnel is running
- Check the URL format is correct
- Verify no typos in the webhook ID
Signature Validation Failing
- Use the exact URL configured in Twilio
- Include query parameters in validation
- Ensure auth token is correct
Missing Parameters
- Different event types have different parameters
- Use optional chaining for safety:
const from = req.body?.From || 'Unknown';
Timeout Errors
- Respond within 15 seconds
- Process heavy operations asynchronously
- Return minimal TwiML if needed
Environment Variables
# Twilio Account Credentials
TWILIO_ACCOUNT_SID=SID
TWILIO_AUTH_TOKEN=TOKEN
# Phone Numbers
TWILIO_PHONE_NUMBER=PHONE_NUMBER
TWILIO_WHATSAPP_NUMBER=whatsapp:PHONE_NUMBER
# Optional
TWILIO_MESSAGING_SERVICE_SID=SID
TWILIO_API_KEY=KEY
TWILIO_API_SECRET=SECRET
Useful Links
Support
Need help with Twilio webhooks?
Responses are generated using AI and may contain mistakes.