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

# Available Webhooks

> Event catalogue and payload shapes for Truedy webhooks

# Available Webhooks

This page lists the webhook **event types** Truedy can send, what each event means, and the **identifiers** you should extract for safe processing.

For end-to-end webhook delivery mechanics (signatures, envelope format, and receiver patterns), see:

* [Securing Webhooks](/guides/securing-webhooks)
* [Webhook Error Handling & Retries](/guides/webhook-error-handling-and-retries)

## Webhook request envelope (what you receive)

Truedy delivers an HTTP `POST` with this JSON shape:

```json theme={null}
{
  "event": "call.ended",
  "timestamp": "2026-03-18T14:30:00.000Z",
  "data": {
    "event": "call.ended",
    "call": {
      "callId": "uv_call_123",
      "agent": { "agentId": "uv_agent_123" }
    },
    "id": "uv_evt_123"
  }
}
```

Notes:

* The top-level `event` is normalized (for example `call.completed` is normalized to `call.ended`).
* The `data` object contains the **original Ultravox event payload**. Depending on the event, identifiers may live at `data.call.*`, `data.batch_*`, `data.voice_*`, etc.
* There is no guarantee that any single optional field is present. Always validate defensively.

## How to dedupe (recommended)

Because webhook delivery can be at-least-once and payloads may repeat, dedupe using one of:

* A unique identifier inside the payload (often `data.id` / `data.eventId`)
* A stable entity identifier for the event family:
  * Call events: prefer `data.call.callId` (or `data.call_id` / `data.callId`)
  * Batch events: prefer `data.batch_id` (or `data.batchId`)
  * Voice events: prefer `data.voice_id` (or `data.voiceId`)

If you cannot find one of these in an event, fall back to a composite key (for example: `event + top_level_timestamp + url + phone_number`), but that may be less reliable.

## Event types

| Event                      | What it means                        | Primary identifiers to extract                                   | Typical use                              |
| -------------------------- | ------------------------------------ | ---------------------------------------------------------------- | ---------------------------------------- |
| `call.started`             | A call has started                   | `data.call.callId` / `data.call.agent.agentId`                   | Update “in progress” UI/CRM              |
| `call.joined`              | The participant joined the call      | `data.call.callId`                                               | Start “call connected” workflows         |
| `call.ended`               | The call has ended (final state)     | `data.call.callId` plus `duration`/`cost`/`summary` when present | Mark call complete; store analytics      |
| `call.billed`              | A billing event for a call           | `data.call.callId`                                               | Reconcile billing / minutes usage        |
| `call.failed`              | The call failed                      | `data.call.callId`                                               | Mark failure; trigger retries or alerts  |
| `batch.status.changed`     | Batch campaign status changed        | `data.batch_id` / `data.batchId`                                 | Update campaign progress                 |
| `batch.completed`          | Batch campaign completed             | `data.batch_id` / `data.batchId`                                 | Final reconciliation                     |
| `voice.training.completed` | Voice training finished successfully | `data.voice_id` / `data.voiceId`                                 | Enable voice for use                     |
| `voice.training.failed`    | Voice training failed                | `data.voice_id` / `data.voiceId`                                 | Surface error to user; allow re-training |

## Call events (payload shapes)

### `call.started`

Use this when you want near-real-time “started” updates.

Example (minimal shape):

```json theme={null}
{
  "event": "call.started",
  "timestamp": "2026-03-18T14:30:00.000Z",
  "data": {
    "call": {
      "callId": "uv_call_123",
      "agent": { "agentId": "uv_agent_123" }
    }
  }
}
```

Recommended extract:

* `uv_call_id = data.call.callId`
* `uv_agent_id = data.call.agent.agentId`

### `call.joined`

Example:

```json theme={null}
{
  "event": "call.joined",
  "timestamp": "2026-03-18T14:31:10.000Z",
  "data": {
    "call": {
      "callId": "uv_call_123",
      "agent": { "agentId": "uv_agent_123" }
    }
  }
}
```

Recommended extract:

* `uv_call_id = data.call.callId`

### `call.ended`

This is the final “call summary” event. Truedy normalizes `call.completed` into `call.ended`.

Example (fields you may see):

```json theme={null}
{
  "event": "call.ended",
  "timestamp": "2026-03-18T14:32:00.000Z",
  "data": {
    "call": {
      "callId": "uv_call_123",
      "agent": { "agentId": "uv_agent_123" },
      "ended": "2026-03-18T14:32:00Z",
      "duration": 72,
      "costUsd": 0.13,
      "summary": "Short call summary",
      "shortSummary": "1-line summary",
      "endReason": "completed",
      "billingStatus": "billed",
      "billedDuration": "72s"
    },
    "id": "uv_evt_456"
  }
}
```

Recommended extract:

* `uv_call_id = data.call.callId` (or `data.call_id` / `data.callId`)
* `duration_seconds` from `data.call.duration` / `data.call.ended` (when present)
* `summary`, `shortSummary`, `costUsd`, `endReason`, `billingStatus` (when present)

### `call.billed`

Example:

```json theme={null}
{
  "event": "call.billed",
  "timestamp": "2026-03-18T14:33:10.000Z",
  "data": {
    "call": { "callId": "uv_call_123" },
    "billingStatus": "billed",
    "billedDuration": "72s"
  }
}
```

Recommended extract:

* `uv_call_id = data.call.callId`

### `call.failed`

Example:

```json theme={null}
{
  "event": "call.failed",
  "timestamp": "2026-03-18T14:30:45.000Z",
  "data": {
    "call": { "callId": "uv_call_123" },
    "endReason": "no_answer"
  }
}
```

Recommended extract:

* `uv_call_id = data.call.callId`
* failure reason fields you see (for example `endReason`)

## Batch / campaign events (payload shapes)

### `batch.status.changed`

This event is primarily for updating campaign progress. Truedy may reconcile state by fetching the latest batch status internally.

Example (minimal shape):

```json theme={null}
{
  "event": "batch.status.changed",
  "timestamp": "2026-03-18T15:00:00.000Z",
  "data": {
    "batch_id": "uv_batch_abc",
    "data": { "status": "COMPLETED" }
  }
}
```

Recommended extract:

* `uv_batch_id = data.batch_id` (or `data.batchId`)
* `new_status = data.data.status` (or `data.status`)

### `batch.completed`

Example:

```json theme={null}
{
  "event": "batch.completed",
  "timestamp": "2026-03-18T16:10:00.000Z",
  "data": {
    "batch_id": "uv_batch_abc",
    "status": "COMPLETED"
  }
}
```

Recommended extract:

* `uv_batch_id = data.batch_id` (or `data.batchId`)

## Voice training events (payload shapes)

### `voice.training.completed`

Example:

```json theme={null}
{
  "event": "voice.training.completed",
  "timestamp": "2026-03-19T10:00:00.000Z",
  "data": {
    "voice_id": "uv_voice_789",
    "timestamp": "2026-03-19T10:00:00Z"
  }
}
```

Recommended extract:

* `uv_voice_id = data.voice_id` (or `data.voiceId`)

### `voice.training.failed`

Example:

```json theme={null}
{
  "event": "voice.training.failed",
  "timestamp": "2026-03-19T10:07:00.000Z",
  "data": {
    "voiceId": "uv_voice_789",
    "error_message": "Training failed: duration too short"
  }
}
```

Recommended extract:

* `uv_voice_id = data.voice_id` (or `data.voiceId`)
* `error_message` / `data.error_message` / `data.data.error_message` (wherever it appears)

## Defensive parsing checklist

When writing your webhook receiver:

* Treat every optional field as optional.
* Support camelCase and snake\_case where possible (`callId` vs `call_id`, etc.).
* Always validate that the identifier you use for dedupe exists and is non-empty.
* Store the dedupe key and the raw payload for debugging.

## Managing webhook endpoints

Webhook endpoints are managed through the Truedy dashboard under **Settings → Webhooks**. You can add, edit, and delete endpoints there. When you create an endpoint, you are given a signing secret — store it immediately as it is only shown once.

<Note>
  Webhook endpoint management (create, list, delete) is not available via the public API. Use the dashboard at **Settings → Webhooks** to configure your endpoints.
</Note>

## Next steps

<Columns>
  <Card title="Securing Webhooks" icon="shield" href="/guides/securing-webhooks">
    Node.js, Python, and Flask signature verification
  </Card>

  <Card title="Error Handling & Retries" icon="rotate" href="/guides/webhook-error-handling-and-retries">
    Idempotent receiver with full database example
  </Card>
</Columns>
