How to Test Twilio Webhooks Locally
What Webhooks Does Twilio Send?
Twilio uses webhooks extensively for SMS, voice, and messaging workflows. When someone sends an SMS to your Twilio number, Twilio sends an HTTP request to your configured webhook URL with the message details. Similarly, when a voice call comes in, Twilio requests TwiML instructions from your webhook.
For SMS, the key webhooks are the incoming message webhook (triggered when someone texts your number) and status callbacks (sent when an outgoing message's status changes — queued, sent, delivered, failed, undelivered). Voice webhooks include the incoming call handler and status callbacks for call events.
Twilio sends most webhook data as application/x-www-form-urlencoded, not JSON. The parameters include From, To, Body (for SMS), CallSid, CallStatus (for voice), and Twilio account identifiers. This form-encoded format can be tricky to debug without proper tooling — ReqPour displays both the raw form data and a parsed key-value view.
Configuring Twilio with ReqPour
In the Twilio Console, navigate to Phone Numbers and select your number. Under "Messaging," set the webhook URL for "A Message Comes In" to your ReqPour endpoint, e.g., https://abc123.reqpour.com/sms. Under "Voice," set the webhook URL for "A Call Comes In."
For status callbacks on outgoing messages, include the StatusCallback parameter when sending:
const client = require('twilio')(accountSid, authToken);const message = await client.messages.create({ body: 'Hello from ReqPour!', to: '+1234567890', from: '+0987654321', statusCallback: 'https://abc123.reqpour.com/sms/status', }); ```
As messages move through Twilio's pipeline, status updates arrive at your ReqPour endpoint. You can watch them arrive in real time in the dashboard — each status change (queued -> sent -> delivered) appears as a separate request.
Handling Twilio Webhooks in Code
Twilio expects your webhook to return TwiML (Twilio Markup Language) for voice calls and optionally for SMS replies. Here is a handler for incoming SMS:
const express = require('express');
const twilio = require('twilio');
const { MessagingResponse } = twilio.twiml;const app = express(); app.use(express.urlencoded({ extended: true }));
app.post('/sms', (req, res) => {
const { From, Body, MessageSid } = req.body;
console.log(SMS from ${From}: ${Body});
const twiml = new MessagingResponse(); twiml.message('Thanks for your message! We received it.');
res.type('text/xml'); res.send(twiml.toString()); });
app.post('/sms/status', (req, res) => {
const { MessageSid, MessageStatus } = req.body;
console.log(Message ${MessageSid} status: ${MessageStatus});
res.sendStatus(200);
});
```
Note that the response must be TwiML (XML), not JSON. The ReqPour dashboard shows both the incoming request and your server's response, so you can verify the TwiML your handler returns is well-formed.
Relaying and Debugging with ReqPour
Start the relay to forward Twilio webhooks to your local server:
npx reqpour relay --to http://localhost:3000Send a test SMS to your Twilio number and watch the request flow through. The ReqPour dashboard shows the form-encoded parameters parsed into a readable format — you can see the From number, Body text, and all the metadata Twilio includes.
One important detail: Twilio validates that webhook responses come from the URL it sent the request to. During development with ReqPour, this validation happens at the ReqPour URL level, so your local server can focus on generating correct responses without worrying about URL matching.
Use replay to test different scenarios: replay an incoming SMS webhook and modify your handler's response logic, or replay status callbacks to verify your delivery tracking code handles all status transitions correctly.
Twilio Security and Validation
Twilio signs webhook requests using your auth token. The X-Twilio-Signature header contains an HMAC-SHA1 hash of the request URL and POST parameters. The official Twilio library provides a validation helper:
const twilio = require('twilio');function validateTwilioRequest(req) { const authToken = process.env.TWILIO_AUTH_TOKEN; const twilioSignature = req.headers['x-twilio-signature']; const url = 'https://abc123.reqpour.com/sms'; // the public URL const params = req.body;
return twilio.validateRequest( authToken, twilioSignature, url, params ); } ```
During development, the URL used for signature validation must match the URL Twilio sent the request to — your ReqPour URL, not localhost. Keep this in mind when configuring your validation logic. You may want to skip validation in development or use the ReqPour URL for the validation check.
Related
Get started with ReqPour
Catch, inspect, and relay webhooks to localhost. Free to start, $3/mo for Pro.