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

# Webhook events

> Reference for every webhook event type, its payload, and what triggers it.

This page lists every webhook event Cerca emits, with the payload shape and a representative JSON example. For the delivery model, signing, and retry semantics, see [Webhooks](/concepts/webhooks). For the receive path, see [Receiving webhooks](/guides/receiving-webhooks).

## Envelope

Every delivery shares the same outer shape. The event-specific payload lives under `data`; everything else identifies which agent, fleet, and thread the event belongs to.

| Field       | Type             | Description                                                                                           |
| ----------- | ---------------- | ----------------------------------------------------------------------------------------------------- |
| `id`        | `string`         | Stable ID for this event. Mirrors the `X-Agent-Delivery-Id` header and is the right key to dedupe on. |
| `event`     | `string`         | The event type, e.g. `thread.completed`. Mirrors `X-Agent-Event`.                                     |
| `timestamp` | `string`         | ISO 8601 timestamp the event was generated at.                                                        |
| `agentId`   | `string`         | Agent the event belongs to.                                                                           |
| `orgId`     | `string`         | Organization that owns the agent.                                                                     |
| `fleetId`   | `string`         | Fleet the subscription is scoped to.                                                                  |
| `threadId`  | `string \| null` | Thread the event belongs to, if any. `null` for agent- and connection-level events.                   |
| `data`      | `object`         | Event-specific payload. See sections below.                                                           |

```json theme={null}
{
  "id": "evt_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "event": "thread.completed",
  "timestamp": "2026-05-04T12:00:00.000Z",
  "agentId": "agent_abc123",
  "orgId": "org_acme",
  "fleetId": "fleet_internal_ops",
  "threadId": "thread_xyz789",
  "data": {
    "status": "idle",
    "previousStatus": "running",
    "result": "Booked the flight and emailed the itinerary."
  }
}
```

## Agent

### `agent.created`

Fired when a new agent is provisioned in the fleet.

| Field   | Type    | Description                                        |
| ------- | ------- | -------------------------------------------------- |
| `agent` | `Agent` | The full agent record, minus internal-only fields. |

```json theme={null}
{
  "agent": {
    "id": "agent_abc123",
    "userId": "user_carol",
    "orgId": "org_acme",
    "fleetId": "fleet_internal_ops",
    "configuration": {
      "model": "claude-sonnet-4-5",
      "instructions": "You are a research assistant.",
      "tools": []
    },
    "metadata": { "team": "research" },
    "createdAt": "2026-05-04T12:00:00.000Z",
    "updatedAt": "2026-05-04T12:00:00.000Z"
  }
}
```

### `agent.updated`

Fired when an agent's configuration or metadata changes. `trigger` distinguishes the two so you can decide whether to invalidate cached config.

| Field     | Type                            | Description                               |
| --------- | ------------------------------- | ----------------------------------------- |
| `trigger` | `"configuration" \| "metadata"` | Which kind of change triggered the event. |
| `agent`   | `Agent`                         | The agent record after the update.        |

```json theme={null}
{
  "trigger": "configuration",
  "agent": {
    "id": "agent_abc123",
    "userId": "user_carol",
    "orgId": "org_acme",
    "fleetId": "fleet_internal_ops",
    "configuration": {
      "model": "claude-sonnet-4-5",
      "instructions": "You are a research assistant. Cite your sources.",
      "tools": []
    },
    "metadata": { "team": "research" },
    "createdAt": "2026-05-04T12:00:00.000Z",
    "updatedAt": "2026-05-04T12:05:00.000Z"
  }
}
```

### `agent.deleted`

Fired when an agent is deleted. The agent record is no longer retrievable, so only the ID is included.

| Field     | Type     | Description             |
| --------- | -------- | ----------------------- |
| `agentId` | `string` | The deleted agent's ID. |

```json theme={null}
{
  "agentId": "agent_abc123"
}
```

## Thread

Thread status values are `"idle"`, `"running"`, `"awaiting"`, or `"closed"`.

### `thread.created`

Fired when a new thread starts on an agent. The `threadId` is on the envelope.

| Field     | Type     | Description                              |
| --------- | -------- | ---------------------------------------- |
| `message` | `string` | The user message that opened the thread. |
| `model`   | `string` | Model the thread was created against.    |

```json theme={null}
{
  "message": "Plan a three-day trip to Lisbon.",
  "model": "claude-sonnet-4-5"
}
```

### `thread.status.changed`

Fired on every thread status transition. Use this when you want to track every state change; use `thread.completed` and `thread.failed` if you only care about runs that finished.

| Field            | Type             | Description                                         |
| ---------------- | ---------------- | --------------------------------------------------- |
| `status`         | `ThreadStatus`   | The new status.                                     |
| `previousStatus` | `ThreadStatus`   | The status before the transition.                   |
| `result`         | `string \| null` | Final result if the thread is closing successfully. |
| `error`          | `string \| null` | Error message if the thread is failing.             |

```json theme={null}
{
  "status": "awaiting",
  "previousStatus": "running",
  "result": null,
  "error": null
}
```

### `thread.completed`

Fired when a `running` or `awaiting` thread settles back to `idle` with a result. A thread can complete multiple times over its lifetime — once per run that finishes successfully. Always paired with a `thread.status.changed` event for the same transition.

| Field            | Type           | Description                                                                                                                                                       |
| ---------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`         | `ThreadStatus` | Currently always `"idle"` at runtime, but the contract is typed as the full `ThreadStatus` union — don't write a typed handler that asserts the literal `"idle"`. |
| `previousStatus` | `ThreadStatus` | The status before the transition (`"running"` or `"awaiting"`).                                                                                                   |
| `result`         | `string`       | The run's final result.                                                                                                                                           |

```json theme={null}
{
  "status": "idle",
  "previousStatus": "running",
  "result": "Booked the flight and emailed the itinerary."
}
```

### `thread.failed`

Fired when a `running` or `awaiting` thread settles back to `idle` with an error. Like `thread.completed`, this can fire more than once over a thread's lifetime.

| Field            | Type           | Description                                                                                                                                                       |
| ---------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`         | `ThreadStatus` | Currently always `"idle"` at runtime, but the contract is typed as the full `ThreadStatus` union — don't write a typed handler that asserts the literal `"idle"`. |
| `previousStatus` | `ThreadStatus` | The status before the transition (`"running"` or `"awaiting"`).                                                                                                   |
| `error`          | `string`       | The error message.                                                                                                                                                |

```json theme={null}
{
  "status": "idle",
  "previousStatus": "running",
  "error": "Tool call exceeded retry budget: search_flights"
}
```

## Turn

A turn is one user-message-and-response exchange inside a thread.

### `turn.created`

Fired when a new turn starts. `seq` orders turns within a thread.

| Field       | Type     | Description                                  |
| ----------- | -------- | -------------------------------------------- |
| `turnId`    | `string` | The new turn's ID.                           |
| `seq`       | `number` | Monotonic sequence number within the thread. |
| `message`   | `string` | The user message that opened this turn.      |
| `startedAt` | `string` | ISO 8601 timestamp the turn started.         |

```json theme={null}
{
  "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "seq": 3,
  "message": "Now book the cheapest of the three.",
  "startedAt": "2026-05-04T12:00:00.000Z"
}
```

### `turn.completed`

Fired when a turn finishes successfully.

| Field         | Type                 | Description                                                                      |
| ------------- | -------------------- | -------------------------------------------------------------------------------- |
| `turnId`      | `string`             | The turn's ID.                                                                   |
| `seq`         | `number`             | Sequence number within the thread.                                               |
| `status`      | `"completed"`        | Always `"completed"` for this event.                                             |
| `result`      | `string \| null`     | The turn's result text, if any.                                                  |
| `error`       | `string \| null`     | Always `null` for completed turns; included for shape parity with `turn.failed`. |
| `tokenUsage`  | `TokenUsage \| null` | Input and output token counts, when available.                                   |
| `completedAt` | `string`             | ISO 8601 timestamp the turn finished.                                            |

```json theme={null}
{
  "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "seq": 3,
  "status": "completed",
  "result": "Booked TAP flight TP202 for $312.",
  "error": null,
  "tokenUsage": {
    "inputTokens": 1842,
    "outputTokens": 256
  },
  "completedAt": "2026-05-04T12:00:08.000Z"
}
```

### `turn.failed`

Fired when a turn ends in failure. Same shape as `turn.completed` with a `"failed"` status and a populated `error`.

| Field         | Type                 | Description                             |
| ------------- | -------------------- | --------------------------------------- |
| `turnId`      | `string`             | The turn's ID.                          |
| `seq`         | `number`             | Sequence number within the thread.      |
| `status`      | `"failed"`           | Always `"failed"` for this event.       |
| `result`      | `string \| null`     | Partial result, if any.                 |
| `error`       | `string \| null`     | Error message describing the failure.   |
| `tokenUsage`  | `TokenUsage \| null` | Token usage up to the point of failure. |
| `completedAt` | `string`             | ISO 8601 timestamp the turn ended.      |

```json theme={null}
{
  "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "seq": 3,
  "status": "failed",
  "result": null,
  "error": "Tool call exceeded retry budget: book_flight",
  "tokenUsage": {
    "inputTokens": 1842,
    "outputTokens": 64
  },
  "completedAt": "2026-05-04T12:00:08.000Z"
}
```

## Message

### `message.created`

Fired when a new user or assistant message is appended to a thread. Tool messages and intermediate assistant content do not fire this event — use thread streaming for the full transcript.

**The message body is not included in this payload.** You only get the role and metadata; to read what was actually said, fetch the message via the API or subscribe to thread streaming.

| Field       | Type                    | Description                                  |
| ----------- | ----------------------- | -------------------------------------------- |
| `turnId`    | `string \| null`        | Turn the message belongs to, if any.         |
| `seq`       | `number`                | Monotonic sequence number within the thread. |
| `role`      | `"user" \| "assistant"` | Who authored the message.                    |
| `createdAt` | `string`                | ISO 8601 timestamp the message was created.  |

```json theme={null}
{
  "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "seq": 7,
  "role": "assistant",
  "createdAt": "2026-05-04T12:00:08.000Z"
}
```

## Approval

### `approval.requested`

Fired when a tool call requires human approval before running. Surface the approval to a human and resolve it via the API; the resolution drives an `approval.resolved` event.

| Field      | Type                           | Description                                               |
| ---------- | ------------------------------ | --------------------------------------------------------- |
| `approval` | `PublicWebhookApprovalRequest` | The approval request, including tool, input, and timeout. |

```json theme={null}
{
  "approval": {
    "id": "approval_01HZX9F4G2N3K7B0Q1WVYE6T8M",
    "threadId": "thread_xyz789",
    "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M",
    "toolName": "book_flight",
    "runtimeToolName": "book_flight",
    "input": {
      "carrier": "TAP",
      "flightNumber": "TP202",
      "priceUsd": 312
    },
    "toolUseId": "toolu_01ABC",
    "toolIndex": 0,
    "status": "pending",
    "timeoutMs": 3600000,
    "timeoutAt": "2026-05-04T13:00:00.000Z",
    "createdAt": "2026-05-04T12:00:00.000Z",
    "resolvedAt": null
  }
}
```

### `approval.resolved`

Fired when an approval reaches a terminal state — approved by a human, denied, timed out, or cancelled because the thread moved on.

| Field        | Type                                                   | Description                    |
| ------------ | ------------------------------------------------------ | ------------------------------ |
| `approvalId` | `string`                                               | ID of the resolved approval.   |
| `decision`   | `"approved" \| "denied" \| "timed_out" \| "cancelled"` | How the approval was resolved. |

```json theme={null}
{
  "approvalId": "approval_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "decision": "approved"
}
```

### `approval.granted`

Fired when a user grants standing approval for a tool, scoped to either a single thread or the whole agent. Subsequent calls to that tool within the scope skip the approval step.

| Field       | Type                  | Description                                              |
| ----------- | --------------------- | -------------------------------------------------------- |
| `scope`     | `"thread" \| "agent"` | Scope the grant applies to.                              |
| `toolName`  | `string`              | Tool the grant covers.                                   |
| `threadId?` | `string`              | Thread the grant applies to, when `scope` is `"thread"`. |
| `grantId`   | `string`              | ID of the new grant.                                     |

```json theme={null}
{
  "scope": "thread",
  "toolName": "book_flight",
  "threadId": "thread_xyz789",
  "grantId": "grant_01HZX9F4G2N3K7B0Q1WVYE6T8M"
}
```

## Schedule

### `schedule.created`

Fired when an agent creates a schedule via the `agent.schedule.*` namespace.

| Field        | Type     | Description                                        |
| ------------ | -------- | -------------------------------------------------- |
| `scheduleId` | `string` | The new schedule's ID.                             |
| `name`       | `string` | Human-readable name.                               |
| `cron?`      | `string` | Cron expression, when the schedule is recurring.   |
| `runAt?`     | `string` | ISO 8601 timestamp, when the schedule is one-shot. |

```json theme={null}
{
  "scheduleId": "sched_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "name": "Weekly research digest",
  "cron": "0 9 * * MON"
}
```

### `schedule.deleted`

Fired when a schedule is removed.

| Field        | Type     | Description                |
| ------------ | -------- | -------------------------- |
| `scheduleId` | `string` | The removed schedule's ID. |

```json theme={null}
{
  "scheduleId": "sched_01HZX9F4G2N3K7B0Q1WVYE6T8M"
}
```

### `schedule.triggered`

Fired when a schedule fires and starts a thread. The new thread's ID is in the payload; the envelope's `threadId` also points at it.

| Field        | Type     | Description                  |
| ------------ | -------- | ---------------------------- |
| `scheduleId` | `string` | Schedule that fired.         |
| `threadId`   | `string` | Thread the schedule started. |

```json theme={null}
{
  "scheduleId": "sched_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "threadId": "thread_xyz789"
}
```

## Connection

### `connection.attached`

Fired when a connection is attached to an agent. Pair this with `connection.detached` to keep your own view of which integrations the agent currently has.

| Field          | Type     | Description                                        |
| -------------- | -------- | -------------------------------------------------- |
| `connectionId` | `string` | Connection that was attached.                      |
| `provider`     | `string` | Provider identifier, e.g. `"google"` or `"slack"`. |

```json theme={null}
{
  "connectionId": "conn_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "provider": "google"
}
```

### `connection.detached`

Fired when a connection is removed from an agent.

| Field          | Type     | Description                   |
| -------------- | -------- | ----------------------------- |
| `connectionId` | `string` | Connection that was detached. |
| `provider`     | `string` | Provider identifier.          |

```json theme={null}
{
  "connectionId": "conn_01HZX9F4G2N3K7B0Q1WVYE6T8M",
  "provider": "google"
}
```

## Test

### `webhook.test`

Synthetic event fired only by `POST /fleets/{fleetId}/webhooks/{id}/test`. The payload `data` is always `{}`. This event is **not** subscribable — you cannot include `"webhook.test"` in a subscription's `events` array on create or update — and it does not appear in `ALL_WEBHOOK_EVENT_TYPES`. Treat it as a probe for verifying the receive path; ignore unknown event types in your handler so a future test event does not fail closed.

```json theme={null}
{}
```
