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

# Webhooks

> Get call events pushed to your server as they happen — signed, retried, per-agent.

Webhooks push [`call.*` events](/developers/webhooks/events) to your server the moment they happen —
call started, ended, recording ready, analysis done, transfers — so you don't have to poll. Every
delivery is **HMAC-signed** so you can verify it came from Vocily.

## Set up an endpoint

Webhooks are configured **per agent**, in the dashboard:

<Steps>
  <Step title="Open the agent">
    Go to **Agents → your agent → Runtime → Webhooks**.
  </Step>

  <Step title="Add an HTTPS endpoint">
    Enter your receiver URL, choose which events to subscribe to, and set (or generate) a **signing
    secret**. Save it — you'll need it to verify signatures.
  </Step>

  <Step title="Return 2xx fast">
    Your endpoint should acknowledge with a `2xx` within 10 seconds, then process asynchronously.
  </Step>
</Steps>

<Info>
  An endpoint is **always active** — to stop delivery, delete it. There's no enable/disable toggle.
</Info>

## The envelope

Every event is a POST with this JSON body:

```json theme={"dark"}
{
  "event_id": "evt_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
  "event_type": "call.ended",
  "api_version": "2026-08-01",
  "occurred_at": "2026-08-04T10:00:47.123Z",
  "workspace_id": "0f32de8b-9243-4a8d-9203-63caffdae3b8",
  "org_id": "7c1e9a02-3b44-4d51-8f2a-1a2b3c4d5e6f",
  "sequence": 42,
  "data": {
    "object": "call",
    "call": { /* the call object — see Events */ }
  }
}
```

| Field         | Description                                                                              |
| ------------- | ---------------------------------------------------------------------------------------- |
| `event_id`    | Stable id for this event, unchanged across retries. **Dedupe on this.**                  |
| `event_type`  | e.g. `call.ended`. Return `200` for types you don't recognize — we add events over time. |
| `api_version` | The dated payload version.                                                               |
| `occurred_at` | When the event happened (ISO 8601, UTC, ms precision).                                   |
| `sequence`    | Per-workspace monotonic counter — use it to order events (don't assume delivery order).  |
| `data.call`   | The [call object](/developers/webhooks/events#the-call-object).                          |

## Delivery headers

```http theme={"dark"}
Content-Type: application/json; charset=utf-8
User-Agent: Vocily-Webhooks/1.0
Vocily-Event-Id: evt_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
Vocily-Event-Type: call.ended
Vocily-Delivery-Id: del_9f8e7d6c5b4a
Vocily-Attempt: 1
Vocily-Workspace-Id: 0f32de8b-9243-4a8d-9203-63caffdae3b8
Vocily-Api-Version: 2026-08-01
Vocily-Timestamp: 1730000000000
Vocily-Signature: t=1730000000000,v1=<hex>
```

Verify `Vocily-Signature` on every request — see [Verifying signatures](/developers/webhooks/signature-verification).
The `Vocily-Event-Id` header is a convenience mirror; **dedupe on the body's `event_id`**, not the header.

## Delivery guarantees

<CardGroup cols={2}>
  <Card title="At-least-once" icon="repeat">
    An event may arrive more than once. Make your handler idempotent — dedupe on `event_id`.
  </Card>

  <Card title="No guaranteed order" icon="arrows-sort">
    Order by `sequence` (or `occurred_at`), not arrival order.
  </Card>

  <Card title="Retries with backoff" icon="clock-rotate-left">
    Up to 3 attempts spaced `0s → 2m → 15m`. A `429`/`503` with `Retry-After` is honored.
  </Card>

  <Card title="Always active" icon="bolt">
    A dead endpoint keeps being retried until attempts are exhausted; it's never auto-disabled. Delete
    it to stop.
  </Card>
</CardGroup>

<Card title="See all events →" icon="list" href="/developers/webhooks/events">
  The 7 `call.*` events with complete payload examples.
</Card>
