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

# Webhook Payloads

> Reference for webhook event types and payload structure

# Webhook Payloads

This page documents the structure of webhook payloads sent by Vocobase when events occur.

## Headers

Every webhook request includes these headers:

| Header                | Description                                |
| --------------------- | ------------------------------------------ |
| `Content-Type`        | `application/json`                         |
| `X-Webhook-Signature` | HMAC-SHA256 signature: `sha256=<hex>`      |
| `X-Webhook-Timestamp` | ISO 8601 timestamp when the event was sent |

## Event: `inbound.route`

This event is sent when a partner-configured pre-answer policy is available for a Vobiz or Plivo inbound call.

### Payload structure

```json theme={null}
{
  "event": "inbound.route",
  "version": "2026-07-04",
  "provider": "plivo",
  "call": {
    "provider_call_id": "call-id",
    "from_number": "+919999999999",
    "to_number": "+918888888888"
  },
  "phone_number": {
    "id": "f1a2c3d4-1111-2222-3333-444455556666",
    "number": "+918888888888"
  },
  "agent": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  },
  "partner": {
    "id": "8f7c95c6-6f2e-4f8f-bd1a-a6c9d9f4e2e3"
  }
}
```

### Headers

Inbound route webhooks for this feature use feature-specific headers:

| Header                 | Description                                 |
| ---------------------- | ------------------------------------------- |
| `Content-Type`         | `application/json`                          |
| `X-Vocobase-Event`     | `inbound.route`                             |
| `X-Vocobase-Timestamp` | Unix milliseconds when the payload was sent |
| `X-Vocobase-Signature` | `v1=<hex-hmac>`                             |

### Response contract

Partners return JSON with an `action` value:

* `ANSWER_WITH_AI`
* `SELECT_AGENT`
* `TRANSFER`
* `REJECT`

When action is `TRANSFER`, `to_number` must be E.164.
When action is `SELECT_AGENT`, `agent_id` must be set.

Vocobase records each resolved route decision for partner observability. Query `GET /api/v2/inbound-route-decisions` to inspect action, source, webhook status, webhook latency, failure reason, transfer target, and created session or phone-call IDs.

## Event: `call.status.updated`

Sent whenever Vocobase learns that a telephony call moved to a new meaningful call status. This event is for real-time orchestration: live dashboards, partner-owned dialer queues, and concurrency release.

`call.status.updated` is sent for phone calls only. Browser WebRTC sessions do not have a carrier call leg and therefore do not emit this event.

### Payload structure

```json theme={null}
{
  "event": "call.status.updated",
  "timestamp": "2026-03-15T14:30:05.000Z",
  "data": {
    "call_id": "c1234567-abcd-1234-abcd-123456789012",
    "session_id": "s1234567-abcd-1234-abcd-123456789012",
    "agent_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "agent_name": "Sales Assistant",
    "client_reference_id": "lead-9821",
    "direction": "outbound",
    "from_number": "+14155551234",
    "to_number": "+919876543210",
    "status": "ringing",
    "disposition": null,
    "answered_by": null,
    "hangup_cause": null,
    "hangup_source": null,
    "previous_status": "pending",
    "terminal": false,
    "concurrency_released": false,
    "account_concurrency": {
      "limit": 10,
      "active": 4,
      "remaining": 6
    },
    "provider": "twilio",
    "provider_status": "ringing",
    "provider_event": "ringing",
    "occurred_at": "2026-03-15T14:30:05.000Z"
  }
}
```

### Field reference

| Field                       | Type                  | Description                                                                                                                                                                                       |
| --------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`                     | string                | Always `"call.status.updated"`                                                                                                                                                                    |
| `timestamp`                 | string (ISO 8601)     | When Vocobase created the webhook event                                                                                                                                                           |
| `data.call_id`              | string (UUID)         | Unique Vocobase call identifier                                                                                                                                                                   |
| `data.session_id`           | string (UUID) or null | Associated session identifier. Normally present for calls started through `POST /calls/start`.                                                                                                    |
| `data.agent_id`             | string (UUID)         | The agent assigned to the call                                                                                                                                                                    |
| `data.agent_name`           | string or null        | Agent display name                                                                                                                                                                                |
| `data.client_reference_id`  | string or null        | Partner-supplied correlation key from `POST /calls/start`, when provided                                                                                                                          |
| `data.direction`            | string                | `outbound` or `inbound`                                                                                                                                                                           |
| `data.from_number`          | string                | Caller phone number                                                                                                                                                                               |
| `data.to_number`            | string                | Destination phone number                                                                                                                                                                          |
| `data.status`               | string                | Normalized Vocobase call status. See [Call status values](#call-status-values).                                                                                                                   |
| `data.disposition`          | string or null        | Final call outcome when known: `completed`, `voicemail`, `user_hangup_no_speech`, `no_answer`, `busy`, `canceled`, `blocked`, `failed`, or `transferred`. Usually `null` on non-terminal updates. |
| `data.answered_by`          | string or null        | Best-known answer classification: `human`, `machine`, `unknown`, or `not_answered`. `machine` indicates an answering machine or voicemail.                                                        |
| `data.hangup_cause`         | string or null        | Raw normalized carrier hangup cause when available                                                                                                                                                |
| `data.hangup_source`        | string or null        | Best-known party or system that ended the call: `caller`, `callee`, `system`, `api`, `carrier`, `error`, or `unknown`                                                                             |
| `data.previous_status`      | string or null        | Previous normalized status, when known                                                                                                                                                            |
| `data.terminal`             | boolean               | `true` when this status is final for the carrier call leg                                                                                                                                         |
| `data.concurrency_released` | boolean               | `true` when partners can start the next call in their own queue                                                                                                                                   |
| `data.account_concurrency`  | object or null        | Account-wide concurrency snapshot at event time. `limit`/`remaining` are `null` for unlimited accounts.                                                                                           |
| `data.provider`             | string or null        | Telephony provider: `twilio`, `plivo`, `vobiz`, `tata_smartflo`, `voicelink`, `sip`, `mcube`, or `exotel`                                                                                         |
| `data.provider_status`      | string or null        | Raw provider status value when available                                                                                                                                                          |
| `data.provider_event`       | string or null        | Raw provider event name when available                                                                                                                                                            |
| `data.occurred_at`          | string (ISO 8601)     | When the status transition was recorded                                                                                                                                                           |

### Call status values

Vocobase normalizes provider-specific call states into one vocabulary:

| Status        | Terminal | Releases concurrency | Meaning                                                                 |
| ------------- | -------: | -------------------: | ----------------------------------------------------------------------- |
| `pending`     |       No |                   No | Vocobase accepted the call request and is waiting for provider progress |
| `ringing`     |       No |                   No | The callee's phone is ringing                                           |
| `in_progress` |       No |                   No | The call was answered and the bot/callee leg is active                  |
| `completed`   |      Yes |                  Yes | An answered call ended normally                                         |
| `failed`      |      Yes |                  Yes | The provider could not connect or continue the call                     |
| `no_answer`   |      Yes |                  Yes | The callee did not answer                                               |
| `busy`        |      Yes |                  Yes | The callee line was busy                                                |
| `canceled`    |      Yes |                  Yes | The call was canceled before completion                                 |

### Provider normalization

Partners should branch on `data.status`, not on provider-specific values. Vocobase stores the raw provider value for debugging.

| Provider value                                                                                                                                                                                                                         | Vocobase status                                                                                                                                                              |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Twilio `initiated`                                                                                                                                                                                                                     | `pending`                                                                                                                                                                    |
| Twilio, Plivo, Vobiz `ringing`                                                                                                                                                                                                         | `ringing`                                                                                                                                                                    |
| Twilio, Plivo, Vobiz `in-progress`; Twilio call-progress `answered`; VoiceLink `call.answered`                                                                                                                                         | `in_progress`                                                                                                                                                                |
| Twilio, Plivo, Vobiz `completed`; answered VoiceLink terminal call                                                                                                                                                                     | `completed`                                                                                                                                                                  |
| Twilio, Plivo, Vobiz `busy`                                                                                                                                                                                                            | `busy`                                                                                                                                                                       |
| Twilio, Plivo, Vobiz `no-answer`                                                                                                                                                                                                       | `no_answer`                                                                                                                                                                  |
| Twilio `canceled`; Plivo/Vobiz `cancel`; VoiceLink canceled classifications                                                                                                                                                            | `canceled`                                                                                                                                                                   |
| Twilio `failed`; Plivo/Vobiz `timeout`; provider error states                                                                                                                                                                          | `failed`                                                                                                                                                                     |
| Tata Smartflo lifecycle payloads (`call_status` is `Answered` or `Missed`; flat snake\_case fields include `call_id`, `uuid`, `hangup_cause`, `start_stamp`, `answer_stamp`, `end_stamp`, `billsec`, `recording_url`, and `direction`) | `Answered` maps to `in_progress` while live and `completed` after hangup; `Missed` maps to `no_answer`; terminal carrier/error causes map to `busy`, `canceled`, or `failed` |

### External queue orchestration

If you run your own dialer, use `call.status.updated` to manage slots:

```js theme={null}
function handleVocobaseWebhook(event) {
  if (event.event === "call.status.updated") {
    const update = event.data;

    updateDialerCall(update.call_id, update.status);

    if (update.concurrency_released) {
      startNextQueuedCall();
    }

    return;
  }

  if (event.event === "session.completed") {
    saveTranscriptAndRecording(event.data);
  }
}
```

Do not wait for `session.completed` to release a dialer slot. `session.completed` is the final rich result after transcript, recording, billing, and extraction processing.

## Event: `session.completed`

Sent when a Vocobase session has reached final post-call processing. For telephony calls, this happens after the call leg ends and the bot/session pipeline has persisted transcript, recording metadata, billing, variables, and extraction results.

### Payload structure

```json theme={null}
{
  "event": "session.completed",
  "timestamp": "2026-03-15T14:32:08.000Z",
  "data": {
    "session_id": "s1234567-abcd-1234-abcd-123456789012",
    "agent_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "agent_name": "Sales Assistant",
    "duration_seconds": 127,
    "credits_used": 2.12,
    "processing_complete": true,
    "pipeline_completed_at": "2026-03-15T14:32:08.000Z",
    "transcript": [
      {
        "role": "bot",
        "content": "Hello! Thanks for taking my call. How can I help you today?",
        "timestamp": "2026-03-15T14:30:01.000Z"
      },
      {
        "role": "user",
        "content": "Hi, I'm interested in your enterprise plan.",
        "timestamp": "2026-03-15T14:30:04.500Z"
      },
      {
        "role": "bot",
        "content": "Great choice! Our enterprise plan includes unlimited agents, priority support, and custom voice training.",
        "timestamp": "2026-03-15T14:30:05.200Z"
      }
    ],
    "recording_url": "https://downloads.example.com/recordings/rec_abc123.wav?signature=...",
    "variables": {
      "callee_name": "Sajal",
      "mobile_number": "+919876543210"
    },
    "extraction": {
      "status": "success",
      "values": {
        "interested": true,
        "callback_at": "2026-05-01T15:00:00Z",
        "outcome": "callback_requested"
      },
      "model": "vocobase-managed",
      "extractedAt": "2026-03-15T14:32:09.840Z",
      "latencyMs": 1840,
      "attempts": 1
    },
    "call": {
      "call_id": "c1234567-abcd-1234-abcd-123456789012",
      "from_number": "+14155551234",
      "to_number": "+919876543210",
      "direction": "outbound",
      "status": "completed",
      "disposition": "completed",
      "answered_by": "human",
      "transferred": false,
      "transferred_at": null,
      "transfer_target": null
    }
  }
}
```

### Field reference

| Field       | Type              | Description                  |
| ----------- | ----------------- | ---------------------------- |
| `event`     | string            | Always `"session.completed"` |
| `timestamp` | string (ISO 8601) | When the webhook was sent    |
| `data`      | object            | Event data (see below)       |

### Data object fields

| Field                   | Type                      | Description                                                                                                                                                                                                                                                                                       |
| ----------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session_id`            | string (UUID)             | Unique session identifier                                                                                                                                                                                                                                                                         |
| `agent_id`              | string (UUID)             | The agent that handled the call                                                                                                                                                                                                                                                                   |
| `agent_name`            | string                    | Agent display name at time of call                                                                                                                                                                                                                                                                |
| `duration_seconds`      | integer                   | Call duration in seconds                                                                                                                                                                                                                                                                          |
| `credits_used`          | number                    | Credits deducted for this call                                                                                                                                                                                                                                                                    |
| `processing_complete`   | boolean                   | `true` when post-call processing has finished. Always `true` on the webhook payload itself. Use it when polling `GET /calls/{id}` to tell final results from in-progress processing.                                                                                                              |
| `pipeline_completed_at` | string (ISO 8601) or null | Timestamp when post-call processing finished. Non-null on the webhook. `null` when polling `GET /calls/{id}` and processing has not completed yet, or on older sessions that pre-date this field.                                                                                                 |
| `transcript`            | array                     | Ordered list of conversation turns (see below)                                                                                                                                                                                                                                                    |
| `recording_url`         | string or null            | Presigned URL for the call recording. `null` if recording was not enabled. URL is valid for 7 days. If it expires, mint a fresh one with `GET /calls/{call_id}/recording-url`.                                                                                                                    |
| `variables`             | object                    | Pre-call variable values that were substituted into the agent's prompt and greeting. Empty object `{}` when no variables were supplied. Echoed for partner-side audit.                                                                                                                            |
| `extraction`            | object or absent          | Post-call extraction result, present only when the agent has a Custom Analysis config defined. Status is `success`, `failed`, or `skipped` (no transcript). Values are typed per the agent's config; missing keys are emitted as `null`. See [Post-call Extraction](#post-call-extraction) below. |
| `call`                  | object or absent          | Call metadata. Present only for telephony sessions started via `POST /calls/start`. **Omitted entirely** for browser WebRTC sessions started via `POST /sessions/webrtc` (no associated phone call).                                                                                              |
| `call.call_id`          | string (UUID)             | Unique call identifier                                                                                                                                                                                                                                                                            |
| `call.from_number`      | string                    | Caller phone number (E.164)                                                                                                                                                                                                                                                                       |
| `call.to_number`        | string                    | Destination phone number (E.164)                                                                                                                                                                                                                                                                  |
| `call.direction`        | string                    | `"outbound"`                                                                                                                                                                                                                                                                                      |
| `call.status`           | string                    | Final call lifecycle/carrier status at the time `session.completed` is emitted. Voicemail is exposed through `call.disposition`, not `call.status`.                                                                                                                                               |
| `call.disposition`      | string or null            | Final call outcome: `completed`, `voicemail`, `user_hangup_no_speech`, `no_answer`, `busy`, `canceled`, `blocked`, `failed`, or `transferred`.                                                                                                                                                    |
| `call.answered_by`      | string or null            | Best-known answer classification: `human`, `machine`, `unknown`, or `not_answered`. `machine` indicates an answering machine or voicemail.                                                                                                                                                        |
| `call.transferred`      | boolean                   | `true` if the bot handed the call off to a human via the cold-transfer flow. See [Transferred calls](#transferred-calls) below.                                                                                                                                                                   |
| `call.transferred_at`   | string (ISO 8601) or null | When the bot left the room and the carrier began bridging the caller to the human. `null` for non-transferred calls.                                                                                                                                                                              |
| `call.transfer_target`  | string or null            | E.164 destination the caller was bridged to. `null` for non-transferred calls.                                                                                                                                                                                                                    |

### Transcript entry format

Each entry in the `transcript` array has:

| Field       | Type              | Description             |
| ----------- | ----------------- | ----------------------- |
| `role`      | string            | `"user"` or `"bot"`     |
| `content`   | string            | What was said           |
| `timestamp` | string (ISO 8601) | When this turn occurred |

<Note>
  `recording_url` is a presigned URL valid for 7 days from the moment the webhook fires. Download and persist the recording on your side if you need long-term retention. If the URL expires before you can download it, call `GET /calls/{call_id}/recording-url` to mint a fresh one with another 7-day window.
</Note>

### Final call status on `session.completed`

`call.status` in `session.completed` reflects the final known carrier state. For real-time intermediate statuses, use `call.status.updated`.

| Status      | Description                               |
| ----------- | ----------------------------------------- |
| `completed` | Call connected and ended normally         |
| `failed`    | Call could not be placed (provider error) |
| `no_answer` | Recipient did not answer                  |
| `busy`      | Recipient's line was busy                 |
| `canceled`  | Call was canceled before connecting       |

### Call disposition values

| Disposition             | Description                                                          |
| ----------------------- | -------------------------------------------------------------------- |
| `completed`             | Human-answered call completed normally                               |
| `voicemail`             | Carrier AMD or bot-side detection classified the answer as voicemail |
| `user_hangup_no_speech` | The callee answered, then hung up quickly before speaking            |
| `no_answer`             | Recipient did not answer                                             |
| `busy`                  | Recipient's line was busy                                            |
| `canceled`              | Call was canceled before connecting                                  |
| `blocked`               | Carrier/provider rejected or blocked the call                        |
| `failed`                | Call failed or ended with an unmapped error outcome                  |
| `transferred`           | Call was handed off to a human via the cold-transfer flow            |

### Transferred calls

When an agent transfers a live call to a human:

1. The bot speaks the configured announcement (`transfer_message`).
2. Vocobase transfers the caller to `transfer_target`.
3. The `session.completed` webhook includes transfer metadata.

The `session.completed` webhook fires once the bot leg ends, not when the caller eventually hangs up with the human. This means:

* `duration_seconds` reflects only the bot leg (caller-on-with-bot time).
* `recording_url`, when present, is the bot-leg recording. The human side is not recorded.
* `call.transferred` is `true`; `call.transferred_at` is the redirect moment; `call.transfer_target` is the destination E.164.

Use `call.transferred` as the cheap discriminator if your downstream automation needs to distinguish a transferred call from a normal hangup.

```json theme={null}
{
  "call": {
    "call_id": "c1234567-abcd-1234-abcd-123456789012",
    "from_number": "+14155551234",
    "to_number": "+919876543210",
    "direction": "outbound",
    "status": "completed",
    "disposition": "completed",
    "answered_by": "human",
    "transferred": true,
    "transferred_at": "2026-05-06T14:32:11.000Z",
    "transfer_target": "+14155559999"
  }
}
```

## Polling `GET /calls/{id}` vs webhook

Both the webhook and `GET /api/v2/calls/{id}` return the same `data` shape, so a partner that can't accept inbound webhooks (or wants to reconcile dropped deliveries) can poll the GET endpoint instead.

A call can appear completed before all transcript, recording, and extraction fields are ready. The `processing_complete` boolean tells you when the response is final:

```
status                processing_complete    meaning
─────────────────────────────────────────────────────────────────────────────
pending/ringing/
in_progress           false                  call still in progress
completed             false                  call ended; post-call processing still running
completed             true                   final — transcript / recording / extraction are stable
failed/no_answer/
busy/canceled         true                   terminal failure; no bot transcript
```

### Recommended polling pattern

```js theme={null}
async function fetchUntilReady(callId) {
  while (true) {
    const { data } = await vocobase.get(`/calls/${callId}`);
    if (data.session?.processing_complete === true) return data;
    if (data.status === 'failed' || data.status === 'no_answer' || data.status === 'busy') return data;
    await sleep(30_000);
  }
}
```

In practice the `session.completed` webhook will reach you before polling kicks in — polling is the fallback for partners that can't accept inbound webhooks, or for reconciliation after a delivery failure.

<Note>
  For sessions created **before this field shipped**, `pipeline_completed_at` will be `null` permanently. Treat `null` + `status: completed` on an older row as "final" — those rows pre-date the signal.
</Note>

## Post-call extraction

When the agent has a **Custom Analysis** config defined, the platform runs an LLM extraction over the transcript after every completed call and ships the result inline in the same `session.completed` webhook — no second webhook to handle. Configure extraction via the dashboard's "Variables & Analytics" tab on the agent editor or via `PUT /api/v2/agent/:id` with `extraction_config`.

### Extraction object structure

```json theme={null}
{
  "status": "success",
  "values": {
    "interested": true,
    "callback_at": "2026-05-01T15:00:00Z",
    "outcome": "callback_requested"
  },
  "model": "vocobase-managed",
  "extractedAt": "2026-03-15T14:32:09.840Z",
  "latencyMs": 1840,
  "attempts": 1
}
```

| Field         | Type              | Description                                                                                                                                         |
| ------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`      | string            | `success`, `failed`, or `skipped`                                                                                                                   |
| `values`      | object or absent  | Extracted fields, typed per the agent's `extraction_config.keys`. Missing fields are emitted as `null`. Absent entirely when `status != 'success'`. |
| `error`       | string or absent  | Failure reason — present only when `status === 'failed'`                                                                                            |
| `model`       | string            | Opaque extraction model identifier. Do not branch business logic on this value.                                                                     |
| `extractedAt` | string (ISO 8601) | When extraction ran                                                                                                                                 |
| `latencyMs`   | integer           | LLM latency in milliseconds                                                                                                                         |
| `attempts`    | integer           | Number of attempts (1 = success on first try; 2-3 = retried)                                                                                        |

<Note>
  Extraction failures **never block webhook delivery**. The unified payload always ships with `extraction.status` indicating outcome — partners that need extraction values can branch on `status === 'success'`, partners that just need transcript + recording can ignore the field.
</Note>

### Replay extraction against a past session

If you edit `extraction_config` and want to backfill an existing session, call `POST /api/v2/calls/{call_id}/extract` with `{ "dry_run": false }`. Optional `dry_run: true` returns the result without persisting. The replay uses the agent's **current** config (not the config at original-call time).

## Pre-call variables

The `variables` field on every webhook echoes the per-call values that were substituted into the agent's prompt and greeting. Configure variable names via the dashboard "Variables & Analytics" tab or `PUT /api/v2/agent/:id` with `variables: ["callee_name", "mobile_number"]`. Supply per-call values via `POST /api/v2/calls/start` body field `variables: { callee_name: "Sajal" }`. Missing values render as empty strings — the platform never errors on missing variables.

## Next steps

<CardGroup cols={2}>
  <Card title="Webhook Setup" icon="gear" href="/webhooks/setup">
    Configure webhook endpoints and verify signatures.
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/webhooks/error-handling">
    Understand retry behavior and failure handling.
  </Card>
</CardGroup>
