Skip to content
SIP.IO
DocsStart free

Webhooks

Subscribe to platform events and SIP.IO POSTs them to your HTTPS endpoint as they happen, signed so you can verify they came from SIP.IO, with automatic retry on transient failures and a full delivery log you can query.

Manage subscriptions via the /v1/webhooks endpoint (scope webhooks):

Terminal window
curl -X POST https://api.sip.io/v1/webhooks \
-H 'x-api-key: sk_…' -H 'content-type: application/json' \
-d '{ "url": "https://acme.com/hooks/sipio", "events": "call.started,call.ended", "secret": "optional" }'
FieldPurpose
urlYour endpoint; must be https://.
eventsComma-separated event names, or * for all (default).
secretHMAC signing secret. If omitted, one is generated and returned once at creation.

GET /v1/webhooks lists your subscriptions (without secrets); DELETE /v1/webhooks with { id } removes one. POST /v1/webhooks/test fires a test event so you can verify your endpoint. GET /v1/webhooks/events returns the full event catalog with a sample payload for each.

13 events across the platform:

EventFires when
call.started / call.answered / call.endedA call is routed, answered, or ends (call.ended carries direction, answered, billsec, hangup_cause, queue_id, agent_id).
agent.stateAn agent’s state changes (login, pause, ready, and so on).
message.received / message.statusAn inbound message arrives, or a delivery status updates.
recording.availableA call recording finishes processing and is ready to play.
fax.received / fax.sent / fax.failedAn inbound fax arrives, or an outbound fax completes or fails.
campaign.started / campaign.completed / campaign.contactAn outbound campaign starts, finishes, or a contact reaches a terminal disposition.
testYou call /v1/webhooks/test.

Every delivery is a JSON POST in this shape:

{
"event": "call.ended",
"account_id": "acc_acme",
"data": { "callId": "…", "direction": "inbound", "answered": 1, "billsec": 184, "hangup_cause": "NORMAL_CLEARING", "queue_id": "q_support", "agent_id": "us_dana", "ts": 1719600000000 },
"ts": 1719600000000
}

Each request also carries x-sipio-event: <event> and content-type: application/json.

If the subscription has a secret, SIP.IO signs the raw request body with HMAC-SHA256 and sends it in the x-sipio-signature header as sha256=<hex>. Recompute it and compare:

import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, header, secret) {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(header || ''), b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
  • Each event is POSTed to every matching active subscription, with a 5-second timeout per attempt.
  • A failed delivery retries automatically, up to 3 attempts with backoff (immediate, then 0.5s, then 2s), for network failures, 429s, and 5xx responses. A 4xx (a bad URL, an auth failure on your side) fails fast instead, since retrying won’t fix it. Make your handler idempotent regardless, and return 2xx quickly.
  • Endpoints must be https://.

GET /v1/webhooks/deliveries returns the outcome of every attempt-set: status, number of attempts, and the last error if it failed. Filter with ?webhook_id=, ?event=, ?failed=1, and ?limit=, so you can see exactly what was delivered, when, and why anything didn’t go through.