·16 min read·twilio, twilio-webhook, sms-webhook, voice-webhook, webhook-testing, hookray

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:

  1. 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)
  2. 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:

StatusMeaning
queuedMessage accepted by Twilio, waiting to be sent
sendingTwilio is sending the message to the carrier
sentCarrier accepted the message
deliveredMessage confirmed delivered to the recipient's device
undeliveredMessage could not be delivered (invalid number, carrier blocked, etc.)
failedMessage failed to send (Twilio-level error)
readRecipient read the message (WhatsApp and some channels only)

Voice Status Callbacks

Fired at various stages of a voice call lifecycle:

StatusMeaning
initiatedCall request received by Twilio
ringingDestination phone is ringing
in-progressCall is connected and active
completedCall ended normally
busyRecipient's line was busy
no-answerCall rang but no one picked up
failedCall could not be connected (invalid number, carrier issue)
canceledCall was canceled before it connected

Messaging Webhooks (Inbound)

Fired when your Twilio number receives an inbound SMS or MMS:

EventWhen It Fires
Incoming messageSomeone sends a text to your Twilio number
Incoming MMSSomeone sends a picture/media message to your Twilio number
Delivery receiptStatus 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)

  1. Log in to the Twilio Console
  2. Navigate to Phone Numbers > Manage > Active Numbers
  3. Click the phone number you want to configure
  4. Scroll to the Messaging section
  5. Under A MESSAGE COMES IN, set the webhook URL for inbound messages
  6. Under PRIMARY HANDLER FAILS, optionally set a fallback URL
  7. 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)

  1. In the Twilio Console, go to Phone Numbers > Active Numbers
  2. Click the phone number
  3. Scroll to the Voice section
  4. Under A CALL COMES IN, set the webhook URL that returns TwiML
  5. Under CALL STATUS CHANGES, set the status callback URL
  6. 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

ParameterDescriptionExample
MessageSidUnique identifier for the messageSM1234567890abcdef...
MessageStatusCurrent status of the messagedelivered
ToRecipient phone number+31612345678
FromSender phone number (your Twilio number)+15551234567
AccountSidYour Twilio Account SIDAC1234567890abcdef...
ApiVersionTwilio API version2010-04-01
SmsSidSame as MessageSid (legacy)SM1234567890abcdef...
SmsStatusSame as MessageStatus (legacy)delivered
ErrorCodeError code if status is undelivered or failed30007
ErrorMessageHuman-readable error descriptionCarrier violation

Inbound SMS Parameters

ParameterDescriptionExample
MessageSidUnique message identifierSM1234567890abcdef...
BodyThe text content of the messageHello, I need help
FromSender's phone number+31612345678
ToYour Twilio number that received the message+15551234567
NumMediaNumber of media attachments0
MediaUrl0URL of the first media attachment (if MMS)https://api.twilio.com/...
MediaContentType0MIME type of the first attachmentimage/jpeg
FromCitySender's city (US/CA numbers)New York
FromStateSender's state (US/CA numbers)NY
FromCountrySender's countryNL

Voice Status Callback Parameters

ParameterDescriptionExample
CallSidUnique identifier for the callCA1234567890abcdef...
CallStatusCurrent status of the callcompleted
ToDestination phone number+31612345678
FromCaller phone number+15551234567
DirectionCall directionoutbound-api
CallDurationDuration of the call in seconds127
DurationBillable duration in seconds120
AccountSidYour Twilio Account SIDAC1234567890abcdef...
TimestampWhen the event occurredThu, 11 Apr 2026 10:23:41 +0000
SequenceNumberOrder of status callbacks for this call3

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

  1. Go to hookray.com/app
  2. A unique webhook URL is generated instantly — no signup required
  3. 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:

  1. In the Twilio Console, go to your phone number's settings
  2. Under Messaging > A MESSAGE COMES IN, paste your HookRay URL
  3. Save the configuration
  4. 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

  1. Twilio takes your webhook URL + all POST parameters, sorts them alphabetically, and concatenates them
  2. Twilio generates an HMAC-SHA1 hash using your Auth Token as the key
  3. Twilio includes the hash as the X-Twilio-Signature HTTP header
  4. 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

  1. URL mismatch — The URL you pass to validateRequest must 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.

  2. Load balancer rewrites — If a load balancer modifies headers or the URL, the signature won't match. Use the X-Forwarded-Proto header to reconstruct the original URL.

  3. 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.

  4. 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:

  1. 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.

  2. URL not publicly accessible — Twilio cannot reach localhost, 192.168.x.x, or any private network address. Use HookRay for instant public URLs.

  3. 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.

  4. Phone number misconfigured — For inbound webhooks, verify the webhook URL is configured on the correct phone number in the Twilio Console.

  5. 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:

  1. Multiple status transitions — This is expected behavior. A message goes through queued -> sending -> sent -> delivered, and each transition triggers a callback. Filter by MessageStatus to only process the statuses you care about.

  2. 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.

  3. Idempotency — Use MessageSid or CallSid combined 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:

  1. 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';
  1. Wrong Auth Token — If you've rotated your Auth Token, update it in your application immediately. The signature is generated with the current token.

  2. 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 CodeMeaningFix
30001Queue overflowReduce sending rate
30002Account suspendedContact Twilio support
30003Unreachable destinationInvalid or disconnected number
30004Message blockedCarrier filtered the message
30005Unknown destinationNumber doesn't exist
30006Landline or unreachable carrierCannot send SMS to this number
30007Carrier violationMessage content triggered carrier filters
30008Unknown errorRetry or contact support
21610Opt-out — recipient unsubscribedDo not send to this number

Best Practices for Production Twilio Webhooks

  1. Validate every request — Always verify the X-Twilio-Signature header in production. Without validation, attackers can spoof webhook deliveries.

  2. Respond immediately — Return a 200 status with an empty <Response></Response> (for TwiML endpoints) or plain OK (for status callbacks) within 15 seconds. Process asynchronously.

  3. Handle all statuses — Don't just wait for delivered. Handle undelivered, failed, busy, no-answer, and canceled to keep your system accurate.

  4. Use StatusCallbackEvent selectively — For voice calls, only subscribe to the events you need. Receiving initiated, ringing, answered, and completed for every call is often unnecessary — completed alone might suffice.

  5. 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.

  6. Set fallback URLs — Configure fallback URLs in the Twilio Console so that if your primary handler fails, Twilio has a backup endpoint to try.

  7. Monitor error codes — Track ErrorCode values in failed delivery callbacks. A spike in 30007 (carrier violation) might indicate your message content is being flagged. A spike in 30003 might 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:

  1. Go to hookray.com/app
  2. Copy your unique webhook URL
  3. Use it as your statusCallback in Twilio API calls
  4. 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