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, the events array must contain the
event types you want (or be empty to subscribe to everything —
except the per-turn events telephony.turn /
web.turn, which are delivered only to endpoints that
name them explicitly).
Two delivery styles carry these events:
- Endpoint deliveries are always non-blocking notifications
with retries: respond with
any 2xx; the envelope carries an
event_idto dedup on. - Blocking exchanges run only on the
legacy single-URL webhook: the
telephony.incoming/web.incomingconfiguration request (webhook-mode numbers and widget keys, 10 s timeout) and webhook-mode tool 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. 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 for
the full request / response schema.
{
"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 transcript, recording URL when available, and billing summary. See
telephony.complete / web.complete for
the payload schema.
telephony.tool
Sent after a telephony call invokes a function tool. 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).
{
"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.
telephony.turn
Sent while a telephony call is in progress, once for each
speech-bearing turn as it happens — the agent's spoken completions and
the caller's transcribed turns. Lets you follow the live conversation
over plain webhooks instead of polling
GET /v1/calls/{call_id}/transcript.
Non-blocking.
{
"data": {
"call_id": 987654321,
"entry_type": "completion",
"from_number": "+14155550199",
"position": 7,
"role": "assistant",
"start_ms": 15200,
"text": "How many employees does your company have?",
"to_number": "+15551234567"
},
"event_id": "8d3f5a2c-7b1e-4c9a-b6d0-2e4f6a8c0d1e",
"type": "telephony.turn"
}| Field | Type | Description |
|---|---|---|
position | integer | The turn's index in the call history — a stable identity for ordering |
role | string | assistant (agent speech) or user (caller speech) |
text | string | The turn's transcript text as known at emission time |
entry_type | string | The underlying history entry type: completion (agent), or user_turn / span (caller) |
start_ms, end_ms | integer | Audio offsets in ms since call start; present only when playout timing was already known at emission time |
web.incoming
The web-channel equivalent of telephony.incoming, sent when a
web widget 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.
{
"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, plus origin_domain,
with from_number set to "web".
web.tool
The web-channel equivalent of telephony.tool. The data carries
origin_domain instead of from_number / to_number.
web.turn
The web-channel equivalent of telephony.turn,
covering web widget calls and builder mic test calls. Same payload
shape, with origin_domain instead of from_number / to_number.
Like telephony.turn, it requires an explicit subscription — it
is never delivered through an empty events array.
Voice events
Custom voice creation is asynchronous. These non-blocking events let you react to a terminal result instead of polling the clone detail endpoint.
voice.ready
Sent when a custom voice finishes processing and can be assigned to an agent.
{
"data": {
"voice": {
"created_at": "2026-07-30T14:12:08.317Z",
"display_name": "Support voice",
"failure_reason": "",
"gender": "female",
"id": "cv_2f6f90b0e9a34ee8b39be7d1",
"language": "en",
"name": "custom:cv_2f6f90b0e9a34ee8b39be7d1",
"status": "ready",
"updated_at": "2026-07-30T14:13:31.605Z"
}
},
"event_id": "2d5f0a61-e9b5-4a3c-b684-29d7d9e4b214",
"type": "voice.ready"
}voice.failed
Sent when custom voice processing reaches a permanent failure.
{
"data": {
"reason": "audio sample could not be processed",
"voice": {
"created_at": "2026-07-30T14:12:08.317Z",
"display_name": "Support voice",
"failure_reason": "audio sample could not be processed",
"gender": "female",
"id": "cv_2f6f90b0e9a34ee8b39be7d1",
"language": "en",
"name": "custom:cv_2f6f90b0e9a34ee8b39be7d1",
"status": "failed",
"updated_at": "2026-07-30T14:13:31.605Z"
}
},
"event_id": "3493e985-1a75-4f77-a10a-e74af440cd31",
"type": "voice.failed"
}| Field | Type | Description |
|---|---|---|
voice.id | string | Custom voice public id |
voice.name | string | Agent voice value in the form custom:<public_id> |
voice.display_name | string | Organization-facing voice name |
voice.language | string | The clone's single language code |
voice.gender | string | male, female, or an empty string |
voice.status | string | ready for voice.ready; failed for voice.failed |
voice.failure_reason | string | Empty on success; processing failure detail on failure |
voice.created_at, voice.updated_at | timestamp | ISO 8601 timestamps |
reason | string | Failure detail; present only on voice.failed |
Quality events
call.graded
Sent whenever an AI grading run completes for a call. Non-blocking.
{
"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 |
issue.reported
Sent when an issue report is created —
either filed by a user from the dashboard (source: "user") or
automatically by call grading (source: "system"). Non-blocking.
{
"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) |
Test call events
test-call.completed
Sent when a
test-call run
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.
{
"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 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.
{
"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 for creating rules, metrics, cooldowns, and the email / Slack channels.