Live
Black Hat USADark ReadingBlack Hat AsiaAI BusinessI Tried Building My Own AI… Here’s What Actually HappenedDEV CommunityFilesystem for AI Agents: What I Learned Building OneDEV CommunityGoogle debuts Gemma 4 open AI models for local use - TestingCatalogGNews AI multimodalAI’s Uncertain Cost Effects in Health Care - American Enterprise Institute - AEIGNews AI healthcareMulti-Model AI Orchestration for Software Development: How I Ship 10x Faster with Claude, Codex, and GeminiDEV CommunityMigrating a Webpack-Era Federated Module to Vite Without Breaking the Host ContractDEV CommunityGet ready for a wave of TBPN clones after its blockbuster OpenAI dealBusiness InsiderFrom MOUs to Markets: Transatlantic Deals Face Reality Testeetimes.comGitHub Copilot Code Review: Complete Guide (2026)DEV CommunityMulti-Stage Continuous DeliveryDEV CommunityBlackSwanX,174 AI agents predict the future by fighting each other,run on OllamaHacker News AI TopPSA: Anyone with a link can view your Granola notes by defaultThe Verge AIBlack Hat USADark ReadingBlack Hat AsiaAI BusinessI Tried Building My Own AI… Here’s What Actually HappenedDEV CommunityFilesystem for AI Agents: What I Learned Building OneDEV CommunityGoogle debuts Gemma 4 open AI models for local use - TestingCatalogGNews AI multimodalAI’s Uncertain Cost Effects in Health Care - American Enterprise Institute - AEIGNews AI healthcareMulti-Model AI Orchestration for Software Development: How I Ship 10x Faster with Claude, Codex, and GeminiDEV CommunityMigrating a Webpack-Era Federated Module to Vite Without Breaking the Host ContractDEV CommunityGet ready for a wave of TBPN clones after its blockbuster OpenAI dealBusiness InsiderFrom MOUs to Markets: Transatlantic Deals Face Reality Testeetimes.comGitHub Copilot Code Review: Complete Guide (2026)DEV CommunityMulti-Stage Continuous DeliveryDEV CommunityBlackSwanX,174 AI agents predict the future by fighting each other,run on OllamaHacker News AI TopPSA: Anyone with a link can view your Granola notes by defaultThe Verge AI
AI NEWS HUBbyEIGENVECTOREigenvector

How to Test Twilio Webhooks with HookCap

DEV Communityby Henry HangApril 2, 20267 min read1 views
Source Quiz

<h1> How to Test Twilio Webhooks with HookCap </h1> <p>Twilio sends webhooks for every significant event in its platform: incoming SMS messages, voice call status changes, delivery receipts, WhatsApp messages, and more. If your app responds to any of these, you need a reliable way to capture and inspect real payloads during development.</p> <p>The core problem is the same as every webhook integration: Twilio needs a public HTTPS URL, but your handler is on <code>localhost</code>. This guide covers using HookCap to solve that during development.</p> <h2> What Twilio Webhooks Are Used For </h2> <p>Twilio webhooks let your server react to events from the Twilio platform:</p> <div class="table-wrapper-paragraph"><table> <thead> <tr> <th>Webhook type</th> <th>When it fires</th> </tr> </thead> <

How to Test Twilio Webhooks with HookCap

Twilio sends webhooks for every significant event in its platform: incoming SMS messages, voice call status changes, delivery receipts, WhatsApp messages, and more. If your app responds to any of these, you need a reliable way to capture and inspect real payloads during development.

The core problem is the same as every webhook integration: Twilio needs a public HTTPS URL, but your handler is on localhost. This guide covers using HookCap to solve that during development.

What Twilio Webhooks Are Used For

Twilio webhooks let your server react to events from the Twilio platform:

Webhook type When it fires

Incoming SMS A message arrives on your Twilio number

SMS Status Callback Delivery status changes (sent, delivered, failed)

Incoming voice call Someone calls your Twilio number

Call Status Callback A call's state changes (initiated, ringing, answered, completed)

WhatsApp messages Incoming WhatsApp messages on your number

Verify service events OTP code sent, check attempts, etc.

Each webhook is an HTTP request Twilio sends to a URL you configure, either in the Twilio Console or via the API.

Step 1: Create a HookCap Endpoint

Go to hookcap.dev, sign up, and create an endpoint. You get a persistent HTTPS URL like:

https://hookcap.dev/e/your-endpoint-id

Enter fullscreen mode

Exit fullscreen mode

This URL works immediately — no local server required. Twilio can reach it from anywhere.

Step 2: Configure Twilio to Send to HookCap

For SMS (Messaging Service or Phone Number)

In the Twilio Console:

  • Go to Phone Numbers → Active Numbers

  • Select the number you want to configure

  • Under Messaging Configuration, set the Incoming Message webhook URL to your HookCap endpoint

  • Set the method to HTTP POST

  • Save

Alternatively, configure via the Twilio API:

const twilio = require('twilio'); const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

await client.incomingPhoneNumbers(process.env.TWILIO_PHONE_NUMBER_SID) .update({ smsUrl: 'https://hookcap.dev/e/your-endpoint-id', smsMethod: 'POST', });`

Enter fullscreen mode

Exit fullscreen mode

For Status Callbacks

Status callbacks are configured per-message when you send:

const message = await client.messages.create({  body: 'Hello from Twilio',  from: process.env.TWILIO_PHONE_NUMBER,  to: '+1234567890',  statusCallback: 'https://hookcap.dev/e/your-endpoint-id', });

Enter fullscreen mode

Exit fullscreen mode

For Voice

In the Twilio Console, go to Phone Numbers → Active Numbers → select your number, then configure the Voice webhook URL.

Step 3: Trigger Events and Inspect Payloads

Send a message to your Twilio number (or call it). HookCap captures the webhook delivery and displays it in real time.

A typical incoming SMS webhook from Twilio looks like:

POST https://hookcap.dev/e/your-endpoint-id

Headers: Content-Type: application/x-www-form-urlencoded I-Twilio-Signature: AbCdEfGhIjKlMnOpQrStUvWxYz= X-Forwarded-For: 3.88.0.0

Body (form-encoded): ToCountry=US ToState=CA SmsMessageSid=SM1234567890abcdef1234567890abcdef NumMedia=0 ToCity=SAN FRANCISCO FromZip=10001 SmsSid=SM1234567890abcdef1234567890abcdef FromState=NY SmsStatus=received FromCity=NEW YORK Body=Hello world FromCountry=US To=+14155551234 ToZip=94102 NumSegments=1 MessageSid=SM1234567890abcdef1234567890abcdef AccountSid=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx From=+12125551234 ApiVersion=2010-04-01`

Enter fullscreen mode

Exit fullscreen mode

Note that Twilio webhooks are form-encoded, not JSON. This matters for signature verification.

A status callback looks different — it includes the delivery status:

MessageSid=SM1234567890abcdef MessageStatus=delivered ErrorCode= AccountSid=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Enter fullscreen mode

Exit fullscreen mode

Step 4: Verify the Twilio Signature

Twilio uses a different signature scheme from Stripe and GitHub. Instead of signing just the body, Twilio signs the full request URL plus all POST parameters.

The algorithm:

  • Take the full URL (including query string)

  • Append each POST parameter (key + value) in alphabetical order

  • Sign the result with your Auth Token using HMAC-SHA1

  • Base64-encode the result

const crypto = require('crypto');

function validateTwilioSignature(authToken, signature, url, params) { // Sort params alphabetically and concatenate key+value const sortedParams = Object.keys(params) .sort() .map(key => ${key}${params[key]}) .join('');

const stringToSign = url + sortedParams;

const expectedSig = crypto .createHmac('sha1', authToken) .update(Buffer.from(stringToSign, 'utf-8')) .digest('base64');

// Constant-time comparison return crypto.timingSafeEqual( Buffer.from(expectedSig), Buffer.from(signature) ); }

// In your Express handler app.post('/webhook/twilio/sms', express.urlencoded({ extended: false }), (req, res) => { const signature = req.headers['x-twilio-signature']; const url = ${req.protocol}://${req.get('host')}${req.originalUrl};

if (!validateTwilioSignature( process.env.TWILIO_AUTH_TOKEN, signature, url, req.body )) { return res.status(403).send('Forbidden'); }

const incomingSms = req.body; console.log(SMS from ${incomingSms.From}: ${incomingSms.Body});

// Respond with TwiML const twiml = new twilio.twiml.MessagingResponse(); twiml.message('Got it!'); res.type('text/xml'); res.send(twiml.toString()); });`

Enter fullscreen mode

Exit fullscreen mode

Or use the official Twilio helper library:

const twilio = require('twilio');

app.post('/webhook/twilio', express.urlencoded({ extended: false }), (req, res) => { const isValid = twilio.validateRequest( process.env.TWILIO_AUTH_TOKEN, req.headers['x-twilio-signature'], https://yourdomain.com${req.path}, // must be the exact URL Twilio used req.body );

if (!isValid) { return res.status(403).send('Forbidden'); }

// Handle the webhook... });`

Enter fullscreen mode

Exit fullscreen mode

Important: The URL Must Match Exactly

Twilio signature verification is sensitive to the URL. If Twilio called https://yourdomain.com/webhook/sms but you reconstruct it as http://yourdomain.com/webhook/sms (wrong protocol) or https://yourdomain.com/webhook/sms?foo=bar (extra query param), verification will fail.

Step 5: Use HookCap Auto-Forward to Test Locally (Pro)

With HookCap's Auto-Forward feature, you can forward captured webhooks to your local server — no tunnel setup needed.

  • In your HookCap dashboard, enable Auto-Forward on your endpoint

  • Set the forward URL to http://localhost:3000/webhook/twilio

  • HookCap proxies incoming webhooks from Twilio to your local server in real time

This is more stable than ngrok or localtunnel for Twilio development because:

  • The HookCap URL stays constant (no need to reconfigure Twilio each time)

  • You can see both the raw Twilio payload AND your server's response in the HookCap dashboard

  • If your local server is down, HookCap still captures the webhook for later replay

Common Twilio Webhook Issues

Signature Verification Fails Behind a Proxy

If your server sits behind a load balancer or reverse proxy, the URL your app sees may not match the URL Twilio used. The host, protocol, or port might differ. Fix this by explicitly reconstructing the URL:

// In Express with a trusted proxy app.set('trust proxy', 1); const url = 
${req.protocol}://${req.hostname}${req.path};

Enter fullscreen mode

Exit fullscreen mode

Or just hardcode the production webhook URL:

const WEBHOOK_URL = 'https://yourdomain.com/webhook/twilio'; const isValid = twilio.validateRequest(authToken, sig, WEBHOOK_URL, req.body);

Enter fullscreen mode

Exit fullscreen mode

Form Encoding vs JSON

Twilio webhooks are application/x-www-form-urlencoded, not JSON. Make sure your framework parses them correctly:

// Correct: use urlencoded parser app.post('/webhook/twilio', express.urlencoded({ extended: false }), handler);

// Wrong: json parser won't parse Twilio's form-encoded body app.post('/webhook/twilio', express.json(), handler);`

Enter fullscreen mode

Exit fullscreen mode

No Response TwiML

For incoming SMS and voice webhooks, Twilio expects a TwiML response. If you return JSON or plain text, Twilio will log an error (though your handler still "worked"). Return valid TwiML:

const twiml = new twilio.twiml.MessagingResponse(); twiml.message('Thank you for your message'); res.type('text/xml').send(twiml.toString());

Enter fullscreen mode

Exit fullscreen mode

For status callbacks, a plain 200 OK with no body is fine.

Debugging Workflow

  • Capture the raw payload in HookCap — See exactly what Twilio sent, including all headers and the form-encoded body

  • Check the SmsStatus or MessageStatus field — Know what state Twilio thinks the message is in

  • Replay to your local handler — Use HookCap replay to send the exact captured payload to localhost

  • Check the Twilio Console error logs — Under Monitor → Logs → Errors, Twilio shows delivery failures with reason codes

HookCap captures the full request including the X-Twilio-Signature header, which you can use to understand what URL Twilio is signing and debug verification failures.

Summary

Testing Twilio webhooks with HookCap:

  • Create a HookCap endpoint and set it as your Twilio webhook URL

  • Trigger real events (send SMS, make calls, or use the Twilio Console "Test" feature)

  • Inspect the form-encoded payload and headers in HookCap

  • Note: Twilio signs URL + sorted params (not just body) — use the Twilio SDK's validation helper

  • Use Auto-Forward to proxy live Twilio webhooks to your local server for integrated testing

Was this article helpful?

Sign in to highlight and annotate this article

AI
Ask AI about this article
Powered by Eigenvector · full article context loaded
Ready

Conversation starters

Ask anything about this article…

Daily AI Digest

Get the top 5 AI stories delivered to your inbox every morning.

More about

versionupdateproduct

Knowledge Map

Knowledge Map
TopicsEntitiesSource
How to Test…versionupdateproductapplicationplatformserviceDEV Communi…

Connected Articles — Knowledge Graph

This article is connected to other articles through shared AI topics and tags.

Knowledge Graph100 articles · 183 connections
Scroll to zoom · drag to pan · click to open

Discussion

Sign in to join the discussion

No comments yet — be the first to share your thoughts!