Webhooks

View as MarkdownOpen in Claude

Webhooks are HTTP requests sent to your server from SignalWire when an event occurs. They help receive information about events like inbound calls to your phone numbers, or messages.

In addition to getting information about events, some webhooks also allow you to tell SignalWire how an event should be handled.

During development, you can use localhost tunneling applications like ngrok to test your webhook handlers locally. See the ngrok quickstart guide to get started.

Configure webhooks for phone numbers

To handle an inbound call or message, you point your phone number at a Resource that holds your webhook URL. When an event arrives, SignalWire requests that URL and your server responds with SWML, the SignalWire Markup Language that tells SignalWire how to handle the call.

What's a Resource?

Resources are the building blocks of SignalWire applications. They include AI Agents, SWML Scripts, cXML Scripts, SIP Endpoints, and more.

1

Create a Resource for your webhook URL

In the SignalWire Dashboard, open the My Resources tab and click + Add, then choose SWML Script.

Selecting a Resource type in the SignalWire Dashboard

The Resource selection menu

Give the script a name, set Handle Calls Using to External URL, and enter your webhook URL in the Primary Script URL field. Click Create to save the Resource.

Configuring a SWML Script Resource with an external URL in the SignalWire Dashboard

A SWML Script Resource configured to fetch SWML from an external webhook URL
2

Assign the Resource to your phone number

Open the Phone Numbers tab and select the number you want to configure.

The Phone Numbers tab of a SignalWire Space showing a list of phone numbers

The purchased phone number list in the SignalWire Dashboard

Click Edit Settings. Under Inbound Call Settings (or Inbound Message Settings for messaging), choose Assign Resource, select the Resource you created, and click Save.

A phone number's settings page showing the Assign Resource option for inbound calls

Assigning a Resource to a phone number's inbound call settings

Status callbacks to keep track of events

Status callbacks are asynchronous HTTP requests SignalWire sends to your server as a call, message, or recording moves through its lifecycle, so your application can react to each state change as it happens.

You subscribe to a status callback programmatically: when you create the call or message, provide a callback URL on the relevant SWML method, and SignalWire posts to it each time the state changes. What you set and the states you receive depend on what you’re tracking:

To trackProvide a callback URL onStates you’ll receive
Voice callscall_state_url on connectcreated, ringing, answered, ended
Messagesstatus_callback on send_sms, or status_url on replyqueued, initiated, sent, delivered, undelivered, failed, read
Recordingsstatus_url on record_callrecording, paused, finished, no_input, error

For voice calls, call_state_events defaults to ['ended'] — set it explicitly to also receive created, ringing, and answered.

SignalWire only marks a message Delivered once it receives a Delivery Receipt (DLR) confirming the message reached the end carrier’s network. A status of Sent means the message left SignalWire successfully. MMS messages do not support DLRs, so they only ever show Sent.

Verify webhook signature

To verify webhooks that originated from SignalWire, SignalWire signs its requests with a digital HMAC security key. You can verify that the security key matches the key documented in your Dashboard’s API Credentials with the validateRequest method.

The API Credentials page in a SignalWire Space showing the signing key

The Signing Key on the API Credentials page
This step is not optional!

For production applications, it is extremely important to verify the webhook signature to ensure the requests are coming from SignalWire and not a malicious third party.

1import { validateRequest } from "@signalwire/js";
2
3// prepare raw body for validation
4app.use(express.json({
5 verify: (req: any, _res, buf) => {
6 req.rawBody = buf.toString();
7 }
8}));
9
10app.post("/mywebhook", (req: any, res) => {
11 const valid = validateRequest(
12 "<SIGNING_KEY_FROM_Dashboard>",
13 req.headers["x-signalwire-signature"] as string,
14 "https://example.ngrok.io/mywebhook", //this should be the public-facing URL of your webhook handler
15 req.rawBody
16 );
17
18 if (!valid) return res.status(401).send("Invalid signature");
19
20 res.sendStatus(200);
21});