> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Voice Webhooks

> The event catalog, delivery headers, signature verification, and retry behaviour.

# Voice Webhooks

The voice server posts signed events to your endpoint for call lifecycle, session and supervisor
activity, and callback-task workflow.

## Delivery headers

Every outbound POST includes:

| Header                | Value                                                 |
| --------------------- | ----------------------------------------------------- |
| `Content-Type`        | `application/json`                                    |
| `User-Agent`          | `stateset-phone-server/<version>`                     |
| `X-Webhook-Event`     | Event name, e.g. `voice.call.started`                 |
| `X-Webhook-Tenant`    | Tenant that owns the event                            |
| `X-Idempotency-Key`   | Stable hash for safe retry                            |
| `X-Webhook-Signature` | HMAC-SHA256 hex of the raw body, when a secret is set |
| `traceparent`         | W3C trace context for correlation                     |

## Verifying signatures

The signature is `hex(HMAC_SHA256(secret, raw_request_body))`.

<Warning>
  Verify against the **raw bytes** of the body, before any JSON parsing, and use a
  **constant-time** comparison. Re-serialising the parsed body will not reproduce the same bytes,
  so the signature will not match.
</Warning>

```js theme={null}
import crypto from 'node:crypto';

function verify(rawBody, signature, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
```

<Tip>
  **Custom tool calls are signed the same way.** An agent's `tools[]` endpoints receive the same
  `X-Webhook-Signature` header, the same secret, and the same scheme — so one verification helper
  covers both webhooks and tool invocations.
</Tip>

Reference implementations for Node.js, Python, and Rust are in the repo's webhook guide.

## Event catalog

### Call lifecycle

| Event                                 | Fires when                                  |
| ------------------------------------- | ------------------------------------------- |
| `voice.call.started` / `call.started` | A call begins                               |
| `call.status`                         | Twilio reports a status change              |
| `call.recording`                      | A recording becomes available               |
| `voice.call.ended` / `call.ended`     | The call ends                               |
| `call.completed`                      | The call reaches a completed terminal state |

### Sessions and supervision

| Event                     | Fires when                                                 |
| ------------------------- | ---------------------------------------------------------- |
| `session.started`         | A voice session opens                                      |
| `session.updated`         | Session state changes                                      |
| `session.action`          | A supervisor acts — monitor, whisper, barge, escalate, end |
| `session.escalated`       | The session escalates to a human                           |
| `session.ended`           | The session closes                                         |
| `voice.session.completed` | The session completes with transcripts persisted           |

### Callback tasks

The follow-up workflow emits a fuller set, useful for building an SLA-aware queue:

`callback_task.updated`, `.dispatch_initiated`, `.dispatch_status`,
`.dispatch_status_updated`, `.completed`, `.reopened`, `.snooze_expired`,
`.follow_up_notification_sent`, `.follow_up_escalated`, `.human_takeover_required`,
`.sla_breached`, `.external_reconciled`.

<Note>
  `callback_task.sla_breached` and `.human_takeover_required` are the two worth alerting on —
  they signal work that will not complete without a person.
</Note>

## Retry and durability

Deliveries are retried, and every POST carries a stable `X-Idempotency-Key`. Key your handler on
it so a retried delivery is a no-op rather than a duplicate.

## Related

* [Voice API](/stateset-voice/api)
* [SDKs](/stateset-voice/sdks) — inbound webhook handlers for Express and Next.js
* [Configuration](/stateset-voice/configuration)
