FeaturesGuidesBlogDocumentationFreeGet Started

Test Twilio Webhooks Locally

Twilio voice and SMS status callbacks on localhost — configure webhook URLs and validate X-Twilio-Signature over HTTPS.

Twilio is unusual among providers here: voice and SMS webhooks must be HTTPS, and Twilio validates X-Twilio-Signature on every callback. This guide assumes you already picked tunnel vs CLI in Test Webhooks Locally.

Try it in 60 seconds

Twilio requires a public HTTPS URL for voice and SMS webhooks. No account signup on the tunnel side:

npm run dev
npx localtolink --port 3000

Set your Twilio phone number's webhook to https://k7m2x9p4q1.localtolink.com/twilio/voice. Call the number. Your local handler receives the POST and returns TwiML. Twilio Monitor shows delivery status if something fails.

How Twilio webhooks reach your app

Twilio sends HTTP POST requests to your server when calls arrive, SMS messages come in, call status changes, or TwiML actions complete. Your local dev server on localhost cannot receive these — Twilio's servers need a public HTTPS URL.

Twilio documentation often references tunnel tools generically — any HTTPS tunnel that forwards POST requests works the same way. LocalToLink provides a public URL without account signup.

Setup

Terminal 1 — your Twilio webhook handler:

npm run dev

Implement routes for the TwiML and status callbacks you configure in Twilio Console.

Terminal 2 — tunnel:

npx localtolink --port 3000

Configure Twilio

In Twilio Console, set webhook URLs for the relevant resource:

ResourceWebhook field
Phone number (SMS)"A message comes in"
Phone number (Voice)"A call comes in"
TwiML AppVoice/SMS request URL
Status callbackStatus callback URL on calls/messages

Example voice webhook URL:

https://k7m2x9p4q1.localtolink.com/twilio/voice

Example SMS webhook URL:

https://k7m2x9p4q1.localtolink.com/twilio/sms

Replace k7m2x9p4q1 with the subdomain LocalToLink assigns.

Handler example (Express)

app.post("/twilio/voice", (req, res) => {
  const twiml = new twilio.twiml.VoiceResponse();
  twiml.say("Hello from your local dev server");
  res.type("text/xml");
  res.send(twiml.toString());
});

Return valid TwiML XML for voice endpoints. Return 200 with an empty body or TwiML for SMS.

Validate Twilio requests

Twilio signs requests with X-Twilio-Signature. Use the official SDK to validate:

import twilio from "twilio";

const valid = twilio.validateRequest(
  process.env.TWILIO_AUTH_TOKEN,
  req.headers["x-twilio-signature"],
  fullUrl, // Must be the full tunnel URL, not localhost
  req.body
);

if (!valid) {
  return res.status(403).send("Forbidden");
}

Critical: Use the full public tunnel URL when validating signatures, not http://localhost:3000/.... Twilio computes the signature against the URL it POSTed to.

Testing the flow

  1. Configure webhook URL in Twilio Console
  2. Call or text your Twilio phone number
  3. Watch your local server logs for the incoming POST
  4. Check Twilio Console → Monitor → Logs for delivery status and errors

Twilio shows HTTP response codes and error messages when webhooks fail.

Twilio Media Streams (WebSocket)

If you use Twilio Media Streams for real-time audio, Twilio connects to your server via WebSocket. LocalToLink supports WebSocket passthrough — configure your stream URL as:

wss://k7m2x9p4q1.localtolink.com/twilio/stream

See WebSocket Through a Tunnel for details.

Verify the tunnel before Twilio

Before saving Console URLs, confirm the public route responds:

curl -sS -o /dev/null -w '%{http_code}\n' https://k7m2x9p4q1.localtolink.com/voice/inbound

Twilio's servers use non-browser user agents, so they skip the visitor interstitial when that policy is enabled. Your manual browser test may show Opening a shared link once — that is normal and does not affect webhook delivery.

Important notes

Troubleshooting

ProblemFix
Signature validation failsUse full tunnel URL in validateRequest, not localhost
Twilio shows 11200 errorTunnel unreachable — confirm it is running
Session endedRestart tunnel and update Console URLs
TwiML not executingConfirm response Content-Type is text/xml
Media Streams disconnectCheck WebSocket endpoint uses wss:// tunnel URL

FAQ

Can I use Twilio's test credentials?
Yes. Test credentials work with tunnel URLs the same way as live credentials for development.

Does Twilio support localhost URLs?
No. Twilio requires publicly reachable HTTPS URLs for webhooks.

How do I debug failed deliveries?
Use Twilio Monitor logs for HTTP status and response body. Add temporary logging in your handler for request payloads.

See Test Webhooks Locally for the general webhook hub.

Try it now

Start a tunnel from your project directory — no account required.

$ npx --yes localtolink@latest

See the full CLI reference for flags and exit codes.

LocalToLink CLI 1.0.1 — verified 2026-09-20