Skip to content

Webhooks

Webhooks push real-time HTTP POST notifications to your server when events happen on your SIPSTACK account — message delivery updates, live call lifecycle (ringing / answered / hangup, for CRM screen-pop), Aura AI voice-agent call outcomes, campaign completion, and custom events fired by your AI voice-agent flows. Instead of polling the API, your endpoint is called the moment the event occurs.

  1. You register an endpoint URL and subscribe it to event types.
  2. When a subscribed event occurs, SIPSTACK POSTs the event JSON to your URL.
  3. Your server responds with a 2xx status within 10 seconds to acknowledge.
  4. Failed deliveries are retried with backoff; endpoints that keep failing are disabled automatically.

Manage webhook endpoints in Switchboard at Account → Integrations → Webhooks — register an endpoint, choose the events it receives, view the delivery log, and send a test event, all from the UI. Webhooks are a plan-gated feature.

You can also manage them through the API, authenticated with an owner/admin portal session (see Authentication):

ActionEndpoint
List endpointsGET /api/webhooks
Create an endpointPOST /api/webhooks with { "url": "https://...", "events": ["message.delivered"] }
Delete an endpointDELETE /api/webhooks/{id}
View delivery logGET /api/webhooks/{id}/deliveries
Send a test eventPOST /api/webhooks/{id}/test
  • The events array selects which events the endpoint receives; ["*"] subscribes to everything (including event types added later). Discover the valid names programmatically with GET /api/v2/events.
  • The create response includes the endpoint’s signing secret — shown only once. Store it securely; you need it to verify deliveries.

You can also manage endpoints with an API key (x-api-key) instead of a portal session, using the /api/v2/webhooks alias — handy for server-to-server setup:

ActionEndpoint
List endpointsGET /api/v2/webhooks
Create an endpointPOST /api/v2/webhooks with { "url": "https://...", "events": ["message.delivered"] }
Delete an endpointDELETE /api/v2/webhooks/{id}

The API-key create response returns the signing secret once (same as the portal endpoint) and echoes the signature header/format so you know exactly what to verify. See the API Reference for full shapes.

The body is the event name plus the event’s fields at the top level:

{
"event": "message.delivered",
"messageId": 12345,
"status": "delivered",
"to": "+14165550100",
"from": "+16135550200",
"campaignId": 42
}

Request headers on every delivery:

HeaderValue
Content-Typeapplication/json
X-Webhook-EventThe event name
X-Webhook-Signaturesha256=<HMAC-SHA256 hex of the raw body>
User-AgentSIPSTACK-Webhook/1.0

The platform exposes thirteen subscribable event types today (plus the synthetic test ping). Subscribe an endpoint only to events on this list (or ["*"] for all of them); an unknown event name is rejected at create time with a 400 that lists the valid names. You can always fetch the live catalog from GET /api/v2/events.

Fired as the carrier reports status changes on outbound SMS, or when an inbound SMS arrives:

EventWhenPayload fields
message.sentMessage accepted by the carriermessageId, status, to, from, campaignId
message.deliveredCarrier confirmed deliverymessageId, status, to, from, campaignId
message.failedDelivery failed or was undeliverablemessageId, status, to, from, campaignId
message.receivedAn inbound SMS arrived on one of your numbersmessageId, from, to, body

campaignId is null for conversational (non-campaign) messages.

Fired when a call’s CDR is finalized:

EventWhenPayload fields
call.completedA call was answered and endedcallId, from, to, duration, billsec, disposition, direction
call.missedA call ended without being answeredcallId, from, to, duration, disposition, direction

Fired live — milliseconds after the PBX sees them, before the CDR is finalized — so a CRM can screen-pop on an incoming call. Every payload carries callId, from, to, and direction (inbound / outbound / internal), plus extension and channel when known.

EventWhenExtra payload fields
call.inboundA call is arriving on one of your numbers (before it’s answered)
call.ringingA call started ringing
call.answeredA call was answered
call.hangupA call ended — the live terminal signal, fired for every directionanswered, duration, billsec, disposition

An inbound call.hangup is followed by the matching finalized call.completed or call.missed above — de-duplicate terminal handling on callId if you subscribe to both the real-time and finalized sets.

Fired by Aura agent calls (distinct from the flow-defined webhook steps below):

EventWhenPayload fields
aura.call.startedThe agent answered and began handling the callcallId, agentId, agentName, from, to, direction, startedAt
aura.call.completedThe agent call endedcallId, agentId, agentName, from, to, direction, billsec, outcome, transferTarget, summary, sentiment, extractedFields
EventWhenPayload fields
campaign.completedA campaign finishes sendingcampaignId, name, messagesSent

AI voice-agent flows can include webhook steps. When a call reaches that step, the event name and payload you defined in the flow are dispatched to your subscribed endpoints. Test calls from the agent builder include "test": true in the payload so you can filter them.

POST /api/webhooks/{id}/test delivers a test event ({ "event": "test", "message": "This is a test webhook delivery" }) to a single endpoint on demand, so you can verify connectivity and signature handling.

Always verify X-Webhook-Signature before trusting a payload — it proves the request came from SIPSTACK and wasn’t tampered with.

const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const expected = 'sha256=' +
crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// Express handler — note express.raw(): verify against the RAW body,
// not a re-serialized parse of it.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!signature || !verifyWebhookSignature(req.body, signature, process.env.SIPSTACK_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// hand off to a queue and respond fast
res.status(200).send('OK');
});
  • Your endpoint must respond 2xx within 10 seconds — respond immediately and process asynchronously. A non-2xx response, a timeout, or a connection error all count as a failed attempt.
  • A failed delivery is retried up to 2 more times: attempt 2 about 1 minute after the failure, attempt 3 about 5 minutes after that — at most 3 attempts per event.
  • Every attempt is logged — event type, HTTP status, response time, attempt number — and queryable via GET /api/webhooks/{id}/deliveries.
  • If you delete or disable an endpoint while retries are pending, those retries are skipped — nothing is delivered to a removed endpoint.
  • When an event exhausts all 3 attempts, the endpoint’s consecutive-failure count increments; after 10 consecutive fully-failed events the endpoint is automatically disabled to protect the queue. Any successful delivery resets the count to zero. Recreate the endpoint (or contact support) once your server is healthy, and use the delivery log to backfill anything you missed.

Retries mean your endpoint can receive the same event more than once. De-duplicate on a natural key (messageId + event, or your flow’s own correlation ID) rather than assuming exactly-once delivery.