Twilio Webhooks: SMS, Voice & Status Callbacks Guide (2026)
Twilio webhooks — called "status callbacks" in Twilio's terminology — notify your application when something happens with your SMS messages, voice calls, or other communication channels. A message gets delivered, a call is answered, a voicemail is left — your server receives an HTTP request with the full event details. No polling the Twilio API. No wondering if that OTP was delivered. Your application knows the status the moment it changes.
This guide covers everything you need to set up, test, and debug Twilio webhooks for SMS, voice, and messaging. Whether you're building a two-factor authentication flow, a customer support line, or an automated notification system, this is the complete reference.
What Are Twilio Webhooks (Status Callbacks)
A Twilio webhook is an HTTP request that Twilio sends to a URL you specify when an event occurs. Twilio uses the term "webhook" for incoming message/call handling and "status callback" for delivery notifications, but the mechanism is the same: Twilio makes an HTTP POST (or GET) to your endpoint with event parameters.
There are two distinct types of Twilio webhooks:
- Request webhooks — Twilio calls your URL when it receives an inbound SMS or voice call, asking your server what to do (respond with TwiML instructions)
- Status callbacks — Twilio calls your URL to report the outcome of an outbound message or call you initiated
For most developers building integrations, status callbacks are the critical piece. They tell you:
- Did that SMS actually get delivered? Or did it fail, get blocked by a carrier, or bounce?
- Did the customer answer the phone call? How long did it last? Was it forwarded to voicemail?
- Was the verification code received? Or should you retry via a different channel?
Without Twilio webhooks, you'd be flying blind after sending a message or initiating a call. The API returns a queued status, and you'd have to poll repeatedly to find out what happened.
Types of Twilio Webhooks
Twilio sends webhooks across multiple products. Here are the ones most developers need:
SMS Status Callbacks
Fired when the delivery status of an outbound SMS changes. The message goes through several states:
| Status | Meaning |
|---|---|
queued | Message accepted by Twilio, waiting to be sent |
sending | Twilio is sending the message to the carrier |
sent | Carrier accepted the message |
delivered | Message confirmed delivered to the recipient's device |
undelivered | Message could not be delivered (invalid number, carrier blocked, etc.) |
failed | Message failed to send (Twilio-level error) |
read | Recipient read the message (WhatsApp and some channels only) |
Voice Status Callbacks
Fired at various stages of a voice call lifecycle:
| Status | Meaning |
|---|---|
initiated | Call request received by Twilio |
ringing | Destination phone is ringing |
in-progress | Call is connected and active |
completed | Call ended normally |
busy | Recipient's line was busy |
no-answer | Call rang but no one picked up |
failed | Call could not be connected (invalid number, carrier issue) |
canceled | Call was canceled before it connected |
Messaging Webhooks (Inbound)
Fired when your Twilio number receives an inbound SMS or MMS:
| Event | When It Fires |
|---|---|
| Incoming message | Someone sends a text to your Twilio number |
| Incoming MMS | Someone sends a picture/media message to your Twilio number |
| Delivery receipt | Status update for an outbound message |
WhatsApp and Conversations Webhooks
If you're using Twilio's Conversations API or WhatsApp Business API, additional webhook events fire for message delivery, read receipts, and participant changes. The configuration pattern is the same.
How to Configure Twilio Webhooks for SMS and Voice
Configuring SMS Webhooks
There are two levels of configuration: per-number (in the console) and per-message (via the API).
Console-Level Configuration (Per Number)
- Log in to the Twilio Console
- Navigate to Phone Numbers > Manage > Active Numbers
- Click the phone number you want to configure
- Scroll to the Messaging section
- Under A MESSAGE COMES IN, set the webhook URL for inbound messages
- Under PRIMARY HANDLER FAILS, optionally set a fallback URL
- Click Save configuration
This sets up inbound message handling. For outbound SMS status callbacks, you configure the URL per message via the API.
API-Level Configuration (Per Message)
When sending an SMS via the Twilio API, include a statusCallback URL:
const twilio = require('twilio');
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const message = await client.messages.create({
body: 'Your verification code is 847293',
from: '+15551234567',
to: '+31612345678',
statusCallback: 'https://yourapp.com/api/webhooks/twilio/sms-status'
});
console.log(`Message SID: ${message.sid}`);
// Output: Message SID: SM1234567890abcdef1234567890abcdef
Twilio will POST to your statusCallback URL every time the message status changes — from queued to sending to sent to delivered (or undelivered/failed).
Configuring Voice Webhooks
Console-Level Configuration (Per Number)
- In the Twilio Console, go to Phone Numbers > Active Numbers
- Click the phone number
- Scroll to the Voice section
- Under A CALL COMES IN, set the webhook URL that returns TwiML
- Under CALL STATUS CHANGES, set the status callback URL
- Click Save
API-Level Configuration (Per Call)
const call = await client.calls.create({
url: 'https://yourapp.com/api/twilio/voice-handler',
to: '+31612345678',
from: '+15551234567',
statusCallback: 'https://yourapp.com/api/webhooks/twilio/call-status',
statusCallbackEvent: ['initiated', 'ringing', 'answered', 'completed'],
statusCallbackMethod: 'POST'
});
console.log(`Call SID: ${call.sid}`);
The statusCallbackEvent array lets you choose exactly which events trigger a webhook. Available events: initiated, ringing, answered, completed.
Twilio Webhook Parameters
Unlike some webhook providers that send JSON, Twilio webhooks send form-encoded parameters (application/x-www-form-urlencoded). Here are the key parameters for each webhook type.
SMS Status Callback Parameters
| Parameter | Description | Example |
|---|---|---|
MessageSid | Unique identifier for the message | SM1234567890abcdef... |
MessageStatus | Current status of the message | delivered |
To | Recipient phone number | +31612345678 |
From | Sender phone number (your Twilio number) | +15551234567 |
AccountSid | Your Twilio Account SID | AC1234567890abcdef... |
ApiVersion | Twilio API version | 2010-04-01 |
SmsSid | Same as MessageSid (legacy) | SM1234567890abcdef... |
SmsStatus | Same as MessageStatus (legacy) | delivered |
ErrorCode | Error code if status is undelivered or failed | 30007 |
ErrorMessage | Human-readable error description | Carrier violation |
Inbound SMS Parameters
| Parameter | Description | Example |
|---|---|---|
MessageSid | Unique message identifier | SM1234567890abcdef... |
Body | The text content of the message | Hello, I need help |
From | Sender's phone number | +31612345678 |
To | Your Twilio number that received the message | +15551234567 |
NumMedia | Number of media attachments | 0 |
MediaUrl0 | URL of the first media attachment (if MMS) | https://api.twilio.com/... |
MediaContentType0 | MIME type of the first attachment | image/jpeg |
FromCity | Sender's city (US/CA numbers) | New York |
FromState | Sender's state (US/CA numbers) | NY |
FromCountry | Sender's country | NL |
Voice Status Callback Parameters
| Parameter | Description | Example |
|---|---|---|
CallSid | Unique identifier for the call | CA1234567890abcdef... |
CallStatus | Current status of the call | completed |
To | Destination phone number | +31612345678 |
From | Caller phone number | +15551234567 |
Direction | Call direction | outbound-api |
CallDuration | Duration of the call in seconds | 127 |
Duration | Billable duration in seconds | 120 |
AccountSid | Your Twilio Account SID | AC1234567890abcdef... |
Timestamp | When the event occurred | Thu, 11 Apr 2026 10:23:41 +0000 |
SequenceNumber | Order of status callbacks for this call | 3 |
Sample Twilio Webhook Payload
Here's what your server actually receives when Twilio sends a Twilio webhook for an SMS delivery:
SMS Status Callback (Delivered)
POST /api/webhooks/twilio/sms-status HTTP/1.1
Host: yourapp.com
Content-Type: application/x-www-form-urlencoded
User-Agent: TwilioProxy/1.1
X-Twilio-Signature: +rEEHt9eHKPFZ9KpRGSocLi2eJo=
SmsSid=SM9a7e4cd5b8f34e29a67d1c8b2e5f0312&SmsStatus=delivered&MessageStatus=delivered&To=%2B31612345678&MessageSid=SM9a7e4cd5b8f34e29a67d1c8b2e5f0312&AccountSid=AC847293f1d5e64a8b9c2d1e3f4a5b6c7d&From=%2B15551234567&ApiVersion=2010-04-01
Parsed into key-value pairs:
{
"SmsSid": "SM9a7e4cd5b8f34e29a67d1c8b2e5f0312",
"SmsStatus": "delivered",
"MessageStatus": "delivered",
"To": "+31612345678",
"MessageSid": "SM9a7e4cd5b8f34e29a67d1c8b2e5f0312",
"AccountSid": "AC847293f1d5e64a8b9c2d1e3f4a5b6c7d",
"From": "+15551234567",
"ApiVersion": "2010-04-01"
}
SMS Status Callback (Failed)
When a message fails, you get additional error information:
{
"SmsSid": "SM9a7e4cd5b8f34e29a67d1c8b2e5f0312",
"SmsStatus": "undelivered",
"MessageStatus": "undelivered",
"To": "+31612345678",
"MessageSid": "SM9a7e4cd5b8f34e29a67d1c8b2e5f0312",
"AccountSid": "AC847293f1d5e64a8b9c2d1e3f4a5b6c7d",
"From": "+15551234567",
"ApiVersion": "2010-04-01",
"ErrorCode": "30007",
"ErrorMessage": "Message filtering - Carrier violation"
}
Voice Call Completed
{
"Called": "+31612345678",
"ToState": "",
"CallerCountry": "US",
"Direction": "outbound-api",
"Timestamp": "Thu, 11 Apr 2026 10:23:41 +0000",
"CallbackSource": "call-progress-events",
"CallerState": "CA",
"ToZip": "",
"SequenceNumber": "4",
"CallSid": "CA3f8e2d1c4b5a6978d0e1f2a3b4c5d6e7",
"To": "+31612345678",
"CallerZip": "94105",
"ToCountry": "NL",
"CalledZip": "",
"ApiVersion": "2010-04-01",
"CalledCity": "",
"CallStatus": "completed",
"Duration": "42",
"From": "+15551234567",
"CallDuration": "47",
"AccountSid": "AC847293f1d5e64a8b9c2d1e3f4a5b6c7d",
"CalledCountry": "NL",
"CallerCity": "San Francisco",
"Caller": "+15551234567",
"CalledState": ""
}
How to Test Twilio Webhooks with HookRay
Testing Twilio webhooks during development presents two challenges: (1) Twilio needs a publicly accessible URL, and (2) triggering real SMS/call events costs money and takes time. HookRay solves the first problem instantly, and combined with Twilio's test credentials, you can test efficiently.
Step 1: Get a HookRay Endpoint
- Go to hookray.com/app
- A unique webhook URL is generated instantly — no signup required
- Copy the URL (it looks like
https://hookray.com/api/wh/your-unique-id)
Step 2: Configure Twilio to Use Your HookRay URL
For SMS status callbacks:
const message = await client.messages.create({
body: 'Test message for webhook inspection',
from: '+15551234567',
to: '+31612345678',
statusCallback: 'https://hookray.com/api/wh/your-unique-id'
});
For inbound SMS handling:
- In the Twilio Console, go to your phone number's settings
- Under Messaging > A MESSAGE COMES IN, paste your HookRay URL
- Save the configuration
- Send a text to your Twilio number from your personal phone
For voice status callbacks:
const call = await client.calls.create({
url: 'http://demo.twilio.com/docs/voice.xml', // Simple TwiML
to: '+31612345678',
from: '+15551234567',
statusCallback: 'https://hookray.com/api/wh/your-unique-id',
statusCallbackEvent: ['initiated', 'ringing', 'answered', 'completed']
});
Step 3: Trigger Events and Inspect
After triggering an event (sending a message, making a call, or receiving an inbound text), switch to your HookRay dashboard. The Twilio webhook request appears instantly:
- View form-encoded parameters parsed into readable key-value pairs
- Check the X-Twilio-Signature header used for request validation
- See the exact User-Agent and other headers Twilio sends
- Compare multiple webhook deliveries as the message/call moves through statuses (queued -> sending -> sent -> delivered)
- Replay webhooks to your local server once your handler is built
Step 4: Build Your Handler
With the captured webhook data from HookRay, build your handler knowing exactly what parameters to expect:
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.post('/api/webhooks/twilio/sms-status', (req, res) => {
const { MessageSid, MessageStatus, To, From, ErrorCode } = req.body;
console.log(`Message ${MessageSid}: ${MessageStatus}`);
switch (MessageStatus) {
case 'delivered':
// Message successfully delivered
markMessageDelivered(MessageSid);
break;
case 'undelivered':
case 'failed':
// Message failed — log error and potentially retry
console.error(`Message failed: ${ErrorCode}`);
handleFailedMessage(MessageSid, ErrorCode);
break;
case 'sent':
// Carrier accepted — delivery pending
break;
}
// Always respond with 200 and empty body or simple TwiML
res.status(200).send('<Response></Response>');
});
For more Twilio-specific webhook patterns, visit our Twilio webhook reference.
Validating Twilio Webhook Signatures
Twilio signs every webhook request with a cryptographic signature using your Auth Token. Verifying this signature is critical in production — without it, anyone who discovers your webhook URL could send fake status updates and manipulate your application state.
How Twilio Signature Validation Works
- Twilio takes your webhook URL + all POST parameters, sorts them alphabetically, and concatenates them
- Twilio generates an HMAC-SHA1 hash using your Auth Token as the key
- Twilio includes the hash as the
X-Twilio-SignatureHTTP header - Your server performs the same calculation and compares the results
Implementation with the Twilio SDK
The Twilio Node.js SDK includes a built-in validator:
const twilio = require('twilio');
function validateTwilioWebhook(req, res, next) {
const twilioSignature = req.headers['x-twilio-signature'];
const authToken = process.env.TWILIO_AUTH_TOKEN;
// The full URL Twilio used (must match exactly, including protocol and port)
const url = 'https://yourapp.com/api/webhooks/twilio/sms-status';
const isValid = twilio.validateRequest(
authToken,
twilioSignature,
url,
req.body
);
if (!isValid) {
console.warn('Invalid Twilio signature — rejecting request');
return res.status(403).send('Forbidden');
}
next();
}
// Use as middleware
app.post('/api/webhooks/twilio/sms-status',
validateTwilioWebhook,
(req, res) => {
// Process the validated webhook...
res.status(200).send('<Response></Response>');
}
);
Common Signature Validation Pitfalls
-
URL mismatch — The URL you pass to
validateRequestmust exactly match what Twilio has on record. If you're behind a reverse proxy that strips the port or changes the protocol (HTTP vs HTTPS), the validation will fail. -
Load balancer rewrites — If a load balancer modifies headers or the URL, the signature won't match. Use the
X-Forwarded-Protoheader to reconstruct the original URL. -
Missing parameters — The validation includes all POST parameters. If middleware modifies or drops parameters before validation, it will fail. Validate before any body-parsing middleware modifies the data.
-
URL-encoded vs. decoded values — Ensure you're comparing against the same encoding Twilio used. The SDK handles this, but custom implementations often get it wrong.
Common Twilio Webhook Issues and Debugging
Webhook URL Not Being Called
Symptoms: You send a message or make a call, but your endpoint never receives a request.
Causes and fixes:
-
Missing statusCallback parameter — For outbound messages and calls, the status callback URL must be explicitly passed in the API call. It's not set automatically from the console.
-
URL not publicly accessible — Twilio cannot reach
localhost,192.168.x.x, or any private network address. Use HookRay for instant public URLs. -
HTTPS certificate issues — Twilio requires a valid SSL certificate in production. Self-signed certificates will be rejected. During development, HTTP is allowed with test credentials.
-
Phone number misconfigured — For inbound webhooks, verify the webhook URL is configured on the correct phone number in the Twilio Console.
-
Account suspended or trial limitations — Trial accounts have restrictions on which numbers you can call/text. Verify your account status.
Receiving Duplicate Callbacks
Symptoms: Your handler receives multiple callbacks for the same event.
Causes and fixes:
-
Multiple status transitions — This is expected behavior. A message goes through
queued->sending->sent->delivered, and each transition triggers a callback. Filter byMessageStatusto only process the statuses you care about. -
Twilio retries on failure — If your endpoint returns a non-2xx status code or times out (within 15 seconds), Twilio retries. Always return 200 quickly.
-
Idempotency — Use
MessageSidorCallSidcombined with the status as an idempotency key:
const eventKey = `${req.body.MessageSid}-${req.body.MessageStatus}`;
if (await isAlreadyProcessed(eventKey)) {
return res.status(200).send('<Response></Response>');
}
Signature Validation Failing
Symptoms: twilio.validateRequest() returns false for legitimate Twilio requests.
Causes and fixes:
- URL mismatch with proxy — If your app is behind nginx, Cloudflare, or a load balancer, the URL Twilio used might differ from what your server sees. Use the original external URL:
// Don't use req.originalUrl — use the actual external URL
const webhookUrl = 'https://yourapp.com/api/webhooks/twilio/sms-status';
-
Wrong Auth Token — If you've rotated your Auth Token, update it in your application immediately. The signature is generated with the current token.
-
GET vs POST — Twilio uses different signing methods for GET and POST requests. Ensure your endpoint and Twilio configuration use the same HTTP method.
Error Codes in Failed Messages
When MessageStatus is undelivered or failed, the ErrorCode parameter tells you why. Common codes:
| Error Code | Meaning | Fix |
|---|---|---|
30001 | Queue overflow | Reduce sending rate |
30002 | Account suspended | Contact Twilio support |
30003 | Unreachable destination | Invalid or disconnected number |
30004 | Message blocked | Carrier filtered the message |
30005 | Unknown destination | Number doesn't exist |
30006 | Landline or unreachable carrier | Cannot send SMS to this number |
30007 | Carrier violation | Message content triggered carrier filters |
30008 | Unknown error | Retry or contact support |
21610 | Opt-out — recipient unsubscribed | Do not send to this number |
Best Practices for Production Twilio Webhooks
-
Validate every request — Always verify the
X-Twilio-Signatureheader in production. Without validation, attackers can spoof webhook deliveries. -
Respond immediately — Return a 200 status with an empty
<Response></Response>(for TwiML endpoints) or plainOK(for status callbacks) within 15 seconds. Process asynchronously. -
Handle all statuses — Don't just wait for
delivered. Handleundelivered,failed,busy,no-answer, andcanceledto keep your system accurate. -
Use StatusCallbackEvent selectively — For voice calls, only subscribe to the events you need. Receiving
initiated,ringing,answered, andcompletedfor every call is often unnecessary —completedalone might suffice. -
Log with MessageSid/CallSid — These SIDs are your primary debugging tool. Log them with every webhook event so you can trace the full lifecycle in both your system and Twilio's dashboard.
-
Set fallback URLs — Configure fallback URLs in the Twilio Console so that if your primary handler fails, Twilio has a backup endpoint to try.
-
Monitor error codes — Track
ErrorCodevalues in failed delivery callbacks. A spike in30007(carrier violation) might indicate your message content is being flagged. A spike in30003might indicate bad data in your phone number list.
Start Testing Your Twilio Webhooks Today
Setting up Twilio webhooks is essential for any application that sends SMS or makes voice calls. But building reliable handlers requires seeing actual webhook payloads, testing every status transition, and debugging signature validation. HookRay gives you instant webhook URLs with real-time inspection, search, and replay — everything you need to build and test Twilio integrations with confidence.
Get started in 5 seconds:
- Go to hookray.com/app
- Copy your unique webhook URL
- Use it as your
statusCallbackin Twilio API calls - See status callbacks arrive in real time
No signup. No credit card. Just fast, reliable webhook testing.
Building a Twilio integration? Try HookRay free — inspect, debug, and replay your Twilio webhooks in real time.
Ready to test your webhooks?
Get a free webhook URL in 5 seconds. No signup required.
Start Testing — Free