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

# Events Catalog

> All webhook event types ThunderPhone emits.

Every webhook body has a `type` field whose value is one of the event
types on this page. When you subscribe to an
[endpoint](/webhooks/endpoints), the `events` array must contain the
event types you want (or be empty to subscribe to everything).

Two delivery styles carry these events:

* **Endpoint deliveries** are always **non-blocking** notifications
  with [retries](/webhooks/overview#delivery-semantics): respond with
  any 2xx; the envelope carries an `event_id` to dedup on.
* **Blocking** exchanges run only on the
  [legacy single-URL webhook](/webhooks/overview): the
  [`telephony.incoming` / `web.incoming`](/webhooks/call-incoming)
  configuration request (webhook-mode numbers and widget keys, 10 s
  timeout) and webhook-mode
  [tool dispatch](/tools/overview#webhook-mode-dispatch). Your response
  shapes the live call.

Example payloads below show the endpoint envelope in its wire order
(keys sorted alphabetically: `data`, `event_id`, `type`); legacy
deliveries carry the same `data` without `event_id`.

## Call events

### `telephony.incoming`

Sent when an inbound call reaches one of your
[phone numbers](/api-reference/phone-numbers). Endpoint deliveries are
fire-and-forget notifications sent for **every** inbound call, whether
the number is agent-configured or webhook-configured. Numbers without
an assigned agent additionally receive the **blocking** configuration
request on the legacy webhook — see
[`telephony.incoming` / `web.incoming`](/webhooks/call-incoming) for
the full request / response schema.

```json theme={null}
{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}
```

### `telephony.complete`

Sent when an inbound or outbound telephony call ends. Non-blocking.
Includes the full transcript, recording URL, and billing summary. See
[`telephony.complete` / `web.complete`](/webhooks/call-complete) for
the payload schema.

### `telephony.tool`

Sent after a telephony call invokes a
[function tool](/tools/overview). Non-blocking audit notification —
the tool has already executed when this event is delivered; it covers
your own function tools (not built-in, knowledge-base, app-connection,
or MCP tools).

```json theme={null}
{
  "data": {
    "arguments": { "date": "2026-04-21" },
    "call_id": 987654321,
    "from_number": "+14155550199",
    "response": {
      "response": { "available_slots": ["9:00 AM", "2:00 PM"] },
      "status": 200
    },
    "to_number": "+15551234567",
    "tool_name": "search_appointments"
  },
  "event_id": "1f0a7c3e-52d4-4a0e-8f4b-b1a6a1c0d9e2",
  "type": "telephony.tool"
}
```

`response` is the executed result: `{"status": <http status>,
"response": <your endpoint's JSON>}` on success, or
`{"status": <status>, "error": "<message>"}` on failure.

### `web.incoming`

The web-channel equivalent of `telephony.incoming`, sent when a
[web widget](/widget/overview) session or a builder mic test call
starts. Endpoint deliveries are fire-and-forget for every web session.
Publishable keys in `mode="webhook"` additionally receive the
**blocking** configuration request on the legacy webhook — that
blocking request has a different shape (`origin_domain`,
`publishable_key_prefix`; no phone numbers). See
[`telephony.incoming` / `web.incoming`](/webhooks/call-incoming).

```json theme={null}
{
  "data": {
    "call_id": 987654322,
    "from_number": "web",
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2",
    "to_number": "+15551234567"
  },
  "event_id": "9a2b4c6d-8e0f-4a1b-9c2d-3e4f5a6b7c8d",
  "type": "web.incoming"
}
```

`from_number` is always the literal `"web"`. For webhook-mode widget
sessions `to_number` is empty (the session's agent number is assigned
after configuration); for builder mic test calls `origin_domain` and
`publishable_key_prefix` are empty.

### `web.complete`

The web-channel equivalent of `telephony.complete`, covering web
widget calls (`direction: "web"`) and builder mic test calls
(`direction: "test"`). Non-blocking. Same payload shape as
[`telephony.complete`](/webhooks/call-complete), plus `origin_domain`,
with `from_number` set to `"web"`.

<Note>
  On the legacy single-URL webhook, builder mic test calls
  historically report as `telephony.complete` — only `direction:
      "web"` calls use the `web.complete` type there. The endpoint system
  maps both web and test calls to `web.*`. Historical payloads may
  contain the legacy `direction` values `widget` or `mic`.
</Note>

### `web.tool`

The web-channel equivalent of `telephony.tool`. The `data` carries
`origin_domain` instead of `from_number` / `to_number`.

***

## Quality events

### `call.graded`

Sent whenever an [AI grading run](/api-reference/calls#ai-call-grading)
completes for a call. Non-blocking.

```json theme={null}
{
  "data": {
    "call_id": 987654321,
    "grade": {
      "call_outcome": "success",
      "created_at": "2026-04-20T18:25:11.002Z",
      "detected_issues": [],
      "graded_at": "2026-04-20T18:25:11.002Z",
      "grader_model": "heuristic-v1",
      "id": 5512,
      "score": 92,
      "status": "completed",
      "summary": "Caller asked about their policy and got a full answer…"
    }
  },
  "event_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
  "type": "call.graded"
}
```

| Field                                 | Type            | Description                                            |
| ------------------------------------- | --------------- | ------------------------------------------------------ |
| `grade.id`                            | integer         | Grade id                                               |
| `grade.score`                         | integer \| null | 0–100                                                  |
| `grade.call_outcome`                  | string          | `success`, `failure`, `unknown`, or `no_conversation`  |
| `grade.summary`                       | string          | One-paragraph summary                                  |
| `grade.detected_issues`               | array           | Issue strings found by the grader                      |
| `grade.status`                        | string          | Always `completed` — only finished runs emit           |
| `grade.grader_model`                  | string          | Which grader produced the result (e.g. `heuristic-v1`) |
| `grade.graded_at`, `grade.created_at` | timestamp       |                                                        |

<Note>
  A call can be graded more than once — a fast heuristic grade is
  often followed by a full model grade once the recording is
  available, and manual regrades are possible. Each completed run
  emits its own `call.graded` event; treat the latest `graded_at` as
  authoritative.
</Note>

### `issue.reported`

Sent when an [issue report](/api-reference/issue-reports) is created —
either filed by a user from the dashboard (`source: "user"`) or
automatically by call grading (`source: "system"`). Non-blocking.

```json theme={null}
{
  "data": {
    "call_id": 987654321,
    "issue_report": {
      "created_at": "2026-04-20T18:25:11.002Z",
      "description": "Five-second silence before responding to the main question.",
      "id": 4321,
      "severity": "warning",
      "source": "system",
      "status": "open",
      "title": "Agent paused too long"
    }
  },
  "event_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
  "type": "issue.reported"
}
```

| Field                   | Type   | Description                                                        |
| ----------------------- | ------ | ------------------------------------------------------------------ |
| `issue_report.severity` | string | `critical`, `warning`, or `info`                                   |
| `issue_report.status`   | string | `open` or `resolved`                                               |
| `issue_report.source`   | string | `user` (filed from the dashboard) or `system` (created by grading) |

<Note>
  Regrading a call rebuilds its system-generated issue reports, which
  re-emits `issue.reported` for the recreated reports. Dedup on
  `call_id` + `title` if you only want one notification per underlying
  problem.
</Note>

***

## Test call events

### `test-call.completed`

Sent when a
[test-call run](/api-reference/test-calls#test-call-run-object)
reaches a terminal status — `completed` or `failed`, including runs
that failed at launch and never produced a call. Non-blocking. Useful
for wiring batch CI runs into your chat/notifications systems.

```json theme={null}
{
  "data": {
    "test_call_run": {
      "call_id": 987654321,
      "completed_at": "2026-04-20T18:25:04.822Z",
      "error_message": "",
      "id": 7110,
      "status": "completed",
      "target_id": 12,
      "target_type": "agent"
    }
  },
  "event_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
  "type": "test-call.completed"
}
```

| Field                         | Type            | Description                                                              |
| ----------------------------- | --------------- | ------------------------------------------------------------------------ |
| `test_call_run.target_type`   | string          | `agent` or `phone_number`                                                |
| `test_call_run.target_id`     | integer         | The agent id or phone-number id the run targeted, matching `target_type` |
| `test_call_run.status`        | string          | `completed` or `failed`                                                  |
| `test_call_run.call_id`       | integer \| null | `null` when the run failed before a call was placed                      |
| `test_call_run.error_message` | string          | Empty on success                                                         |

***

## Alert events

### `alert.triggered`

Sent when an [alert rule](/guides/alerts) with the **Deliver to
developer webhooks** channel enabled crosses its threshold.
Non-blocking. A rule fires once and then respects its cooldown, so a
sustained breach produces one event per cooldown window.

```json theme={null}
{
  "data": {
    "comparator": "lt",
    "event_id": "b8e6a1d4-2c3f-4a5b-9c8d-7e6f5a4b3c2d",
    "fired_at": "2026-04-20T18:00:00+00:00",
    "metric": "success_rate",
    "metric_value": 71.4,
    "rule_id": "d2c3b4a5-6f7e-4d8c-9b0a-1c2d3e4f5a6b",
    "rule_name": "Success rate below 80%",
    "threshold": 80.0,
    "window_hours": 24
  },
  "event_id": "4d5e6f7a-8b9c-4d0e-9f1a-2b3c4d5e6f7a",
  "type": "alert.triggered"
}
```

| Field                  | Type         | Description                                                                       |
| ---------------------- | ------------ | --------------------------------------------------------------------------------- |
| `event_id` (in `data`) | UUID         | The alert **firing** id — distinct from the envelope's delivery `event_id`        |
| `rule_id`, `rule_name` | UUID, string | The rule that fired                                                               |
| `metric`               | string       | `success_rate`, `failure_rate`, `avg_score`, `call_volume`, or `suite_regression` |
| `comparator`           | string       | `lt`, `lte`, `gt`, or `gte`                                                       |
| `metric_value`         | number       | The metric's value over the window when the rule fired                            |
| `threshold`            | number       | The configured threshold                                                          |
| `window_hours`         | integer      | Trailing evaluation window                                                        |
| `fired_at`             | timestamp    |                                                                                   |

See the [Alerts guide](/guides/alerts) for creating rules, metrics,
cooldowns, and the email / Slack channels.

***

## Related

<CardGroup cols={2}>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/webhooks/call-incoming">
    The blocking inbound-call payload you must respond to.
  </Card>

  <Card title="telephony.complete / web.complete" icon="phone-slash" href="/webhooks/call-complete">
    Post-call transcript and metrics.
  </Card>

  <Card title="Webhook endpoints" icon="bolt" href="/webhooks/endpoints">
    Subscribe a URL to a subset of these events.
  </Card>

  <Card title="Function Tools" icon="screwdriver-wrench" href="/tools/overview">
    How `telephony.tool` / `web.tool` events are generated.
  </Card>
</CardGroup>
