> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes. # Webhooks > An introduction to using webhooks to receive information and events about calls and messages. [Webhooks](https://en.wikipedia.org/wiki/Webhook) 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](https://ngrok.com) to test your webhook handlers locally. See [the ngrok quickstart guide](https://ngrok.com/docs/getting-started) to get started. ## Configure webhooks for phone numbers To handle an inbound call or message, you point your phone number at a [Resource](/docs/platform/resources) that holds your webhook URL. When an event arrives, SignalWire requests that URL and your server responds with [SWML](/docs/swml), the SignalWire Markup Language that tells SignalWire how to handle the call. ```mermaid flowchart LR A["Incoming message"] --> B[SignalWire] B --> C["Webhook HTTP request to your server"] C --> D[[Your webhook handler]] D --> F["Generate SWML to reply to the message"] F -- "200 OK, SWML" --> B ``` > **What's a Resource?** > > [Resources](/docs/platform/resources) are the building blocks of SignalWire applications. They include AI Agents, SWML Scripts, cXML Scripts, SIP Credentials, and more. ### Create a Resource for your webhook URL In the SignalWire Dashboard, open the **My Resources** tab and click **+ Add**, then choose **SWML Script**. In **My Resources**, select **+ Add**, then choose **Script** > **SWML Application**. 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. In the **New SWML Application** form, enter a name, set **Handle Calls Using** to **External URL**, enter the webhook endpoint in **Primary Script URL**, and create the Resource. ### Assign the Resource to your phone number Open the **Phone Numbers** tab and select the number you want to configure. Open **Phone Numbers** > **Purchased**, search for the number if necessary, and select its name to open its settings. Click **Edit Settings**. Under **Inbound Call Settings** (or **Inbound Message Settings** for messaging), choose **Assign Resource**, select the Resource you created, and click **Save**. On the phone number's edit page, select **+ Assign Resource** under **Inbound Call Settings** for calls or **Inbound Message Settings** for messages. Choose the webhook Resource and save. #### [Mapping numbers](/docs/server-sdks/guides/mapping-numbers) A full walkthrough of connecting a phone number to your application. #### [Phone numbers](/docs/platform/phone-numbers) How to purchase and manage phone numbers in your SignalWire Space. #### [Handling calls from code](/docs/swml/guides/remote-server) Learn how to handle incoming calls and messages from code. ## 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, keeping your application informed of each state change. ```mermaid flowchart LR A["Inbound Call"] --> B[SignalWire] B --> C["Webhook HTTP request to your server"] C --> D[[Your webhook handler]] D --> E[(Database)] D --> F[Other internal services] ``` 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 track | Provide a callback URL on | States you'll receive | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | **Voice calls** | `call_state_url` on [`connect`](/docs/swml/reference/calling/connect) | `created`, `ringing`, `answered`, `ended` | | **Messages** | `status_callback` on [`send_sms`](/docs/swml/reference/calling/send-sms), or `status_url` on [`reply`](/docs/swml/reference/messaging/reply) | `queued`, `initiated`, `sent`, `delivered`, `undelivered`, `failed`, `read` | | **Recordings** | `status_url` on [`record_call`](/docs/swml/reference/calling/record-call) | `recording`, `paused`, `finished`, `no_input`, `error` | > **Note** > > For voice calls, `call_state_events` defaults to `['ended']` — set it explicitly to also receive `created`, `ringing`, and `answered`. > **Info** > > 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**. ### Status callbacks are advisory \[#status-callback-reliability] Status callbacks are best-effort notifications, not a reliable realtime signal. They are delivered asynchronously over HTTP, so a callback can arrive late, arrive after a retry, or not arrive at all — if your server is unreachable when SignalWire sends the request, the callback is simply lost, and nothing notifies your application that it was missed. Because a missed callback is invisible to the receiver, never gate time-critical or business-critical actions (billing, compliance steps, call teardown, failover) solely on receiving one. For critical paths, use a mechanism whose failure modes are visible to your application: * **[RELAY over WebSocket](/docs/server-sdks/reference/python/relay)** (also for [TypeScript](/docs/server-sdks/reference/typescript/relay)) — events arrive over a persistent connection, so your client knows immediately when it is disconnected and can fail safe. See the RELAY events reference for [Python](/docs/server-sdks/reference/python/relay/events) and [TypeScript](/docs/server-sdks/reference/typescript/relay/events). * **REST polling and reconciliation** — treat a callback as a hint: confirm the state with a `GET` before acting, and run a periodic sweep to catch missed callbacks. Use [Retrieve a call](/docs/compatibility-api/rest/calls/retrieve-a-call) and [Retrieve a message](/docs/compatibility-api/rest/messages/retrieve-message) on the Compatibility API, or [voice logs](/docs/apis/rest/voice-logs/get-voice-log) and [message logs](/docs/apis/rest/message-logs/get-message-log) on the SignalWire REST API. * **In-call flow logic** — act on results inside the call flow instead of out-of-band. SWML's [`connect`](/docs/swml/reference/calling/connect#variables) sets result variables (`connect_result`) you can branch on, and cXML's [`` `action` URL](/docs/compatibility-api/cxml/reference/voice/dial#dial_action) receives the dial outcome as part of document execution. #### [Message status callback](/docs/apis/rest/webhooks/message-status-callback) The full field reference and status values for outbound message status callbacks. #### [10DLC status callback](/docs/apis/rest/webhooks/ten-dlc-status-callback) Receive 10DLC campaign registration status updates via webhooks. ## 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](https://my.signalwire.com?page=credentials) with the `validateRequest` method. In the Dashboard, open **API Credentials**, find **Signing Key**, and select **Show** to reveal the key for the current project. > **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. ```js import { validateRequest } from "@signalwire/js"; // prepare raw body for validation app.use(express.json({ verify: (req: any, _res, buf) => { req.rawBody = buf.toString(); } })); app.post("/mywebhook", (req: any, res) => { const valid = validateRequest( "", req.headers["x-signalwire-signature"] as string, "https://example.ngrok.io/mywebhook", //this should be the public-facing URL of your webhook handler req.rawBody ); if (!valid) return res.status(401).send("Invalid signature"); res.sendStatus(200); }); ``` > An introduction to using webhooks to receive information and events about calls and messages.