BlogTwilio Webhooks

How to Test Twilio Webhooks

Twilio sends webhook callbacks for SMS delivery status, incoming messages, voice calls, and other communication events.

Looking for the broader picture? See the 7 best webhook testing tools (2026), or if you're already on Webhook.site, the 60-second migration to HookRay.

Twilio Official Webhook Docs

1. Twilio Webhook Events

Twilio can send the following webhook events to your endpoint:

message.sent
message.delivered
message.failed
message.received
call.initiated
call.ringing
call.answered
call.completed
recording.completed
transcription.completed

2. Set Up a Test Endpoint with HookRay

Follow these steps to start receiving Twilio webhooks for testing:

  1. Go to HookRay and click "Start Testing — Free" to get your unique webhook URL.
  2. Copy the URL (e.g., https://h.hookray.com/abc123).
  3. In your Twilio dashboard, navigate to the webhook settings and paste the HookRay URL as your endpoint.
  4. Select the events you want to receive (see list above).
  5. Trigger a test event — HookRay will show the incoming webhook in real-time.

3. Sample Twilio Webhook Payload

Here's an example of what a Twilio webhook payload looks like:

payload.json
{
  "MessageSid": "SM1234567890abcdef",
  "AccountSid": "AC1234567890abcdef",
  "From": "+15551234567",
  "To": "+15559876543",
  "Body": "Hello from Twilio!",
  "MessageStatus": "delivered"
}

4. How to Verify Twilio Webhook Signatures

Signing details
Algorithm
HMAC-SHA1
Header
X-Twilio-Signature
Encoding
base64

Twilio signs `{full_request_url}` concatenated with each form-encoded POST parameter sorted alphabetically by name (key+value, no separator). The exact URL must be the public-facing one Twilio called — not localhost.

Node.js (Express)

// Easiest path — let the official SDK assemble the signed string.
import twilio from 'twilio';
import express from 'express';

const app = express();

app.post(
  '/webhooks/twilio',
  express.urlencoded({ extended: false }),
  (req, res) => {
    const signature = req.headers['x-twilio-signature'] as string;
    const fullUrl =
      'https://' + req.headers.host + req.originalUrl; // exact URL Twilio called
    const valid = twilio.validateRequest(
      process.env.TWILIO_AUTH_TOKEN!,
      signature,
      fullUrl,
      req.body, // form-encoded params
    );
    if (!valid) return res.status(401).send('invalid signature');
    res.set('Content-Type', 'text/xml').send(
      '<Response><Message>Got it.</Message></Response>',
    );
  },
);

Python (FastAPI)

# Use the official twilio-python validator — it implements the
# alphabetical-sort + concatenation rules correctly.
import os
from twilio.request_validator import RequestValidator
from fastapi import FastAPI, Request, HTTPException

validator = RequestValidator(os.environ['TWILIO_AUTH_TOKEN'])
app = FastAPI()

@app.post("/webhooks/twilio")
async def twilio_webhook(request: Request):
    form = dict(await request.form())
    signature = request.headers.get('x-twilio-signature', '')
    full_url = str(request.url)  # must be the public-facing URL
    if not validator.validate(full_url, form, signature):
        raise HTTPException(status_code=401, detail='invalid signature')
    return {'ok': True}
Watch out: Twilio's signing string includes the full request URL. Reverse proxies that change the host header (Cloudflare, Vercel, ngrok) can break verification — use the URL Twilio actually called, not the internal one your handler sees.

Capture a real Twilio webhook with HookRay first, then replay the captured request against your verifier locally — that way you can iterate on the verification code without re-triggering events in Twilio. Read Twilio's official signing docs for the canonical reference, or see the cross-service signature verification guide for Ruby and timing-safe comparison patterns.

5. How Twilio Retries Failed Webhooks

Retry policy
Max attempts
Configurable (1 retry default)
Total window
~15 seconds default
Backoff
Configurable per webhook (Slow / Fast retry)
Retries on
Non-2xx, timeouts (15s)
Stops on
Any 2xx, OR after the configured retry count
Watch out: Twilio's defaults are MUCH less generous than Stripe/Shopify. If you're on the default config, your handler effectively has one shot — make it count. Configure 'Slow Retry' on the webhook for an hour-long retry window if this matters.

If your handler is going to see the same Twilio event multiple times, you need idempotency by event ID and the right response codes — see the Webhook Retry Strategies guide for the full pattern (idempotency tables, dead-letter queues, replaying captured events for tests). Cross-reference with Twilio's official retry docs before relying on these numbers in production.

6. Frequently Asked Questions

How do I test Twilio webhooks without deploying?

Use HookRay to get an instant public webhook URL. Paste it into your Twilio dashboard's webhook configuration, trigger an event, and watch the payload arrive in real time. No code, no ngrok, no deployment required. The free tier captures 100 requests per month and works on all Twilio event types.

Why aren't my Twilio webhooks arriving?

The four most common causes: (1) the endpoint URL isn't publicly accessible — Twilio can't reach localhost; (2) the wrong events are subscribed in your Twilio dashboard; (3) signature verification is rejecting the request before your handler runs; (4) Twilio can't reach your server because of a firewall, expired SSL certificate, or wrong DNS. Use HookRay's URL to isolate which of these four is failing — if HookRay receives the webhook, the problem is in your handler. If HookRay doesn't, the problem is in Twilio configuration.

Why am I getting 400 or 500 errors from my Twilio webhook?

Twilio reports the response status your endpoint returned. HookRay accepts any payload and returns 200 OK by default, so if you see 400/500 in your Twilio dashboard while pointing at HookRay, the issue is in Twilio's configuration (wrong event, malformed signing secret, etc.). If you point at your own endpoint and get 400/500, the issue is in your handler — capture the request with HookRay, replay it locally, and debug from the captured payload.

How do I verify Twilio webhook signatures?

Twilio signs each webhook request with a shared secret. Capture the raw headers and body using HookRay, then verify the signature in your application using Twilio's SDK or a standard HMAC library. Once verification works against HookRay-captured data, you can safely deploy. Twilio's docs (linked above) cover the exact signing algorithm.

Can I replay a captured Twilio webhook?

Yes — HookRay's replay feature re-sends any captured webhook to a different endpoint with one click. This is the fastest way to fix a buggy handler: capture the payload once, fix your code, and replay until it works. No need to re-trigger the event in Twilio.

7. Next Steps

  • Use HookRay's webhook replay feature to re-send captured webhooks while building your handler
  • Enable smart parsing (Pro plan) to see Twilio-specific fields highlighted automatically
  • Check the Twilio webhook documentation for the complete event reference

Ready to test Twilio webhooks?

Get a free webhook URL in 5 seconds. No signup required.

Start Testing Twilio Webhooks — Free

Free PDF: Webhook Testing Cheat Sheet 2026

One-page reference for 50+ APIs — canonical events, signing methods, sample payloads. Print it, pin it, share it.

📄 Download the cheat sheet (PDF, 180KB)