ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Webhooks

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:

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"
}
FieldTypeDescription
positionintegerThe turn's index in the call history — a stable identity for ordering
rolestringassistant (agent speech) or user (caller speech)
textstringThe turn's transcript text as known at emission time
entry_typestringThe underlying history entry type: completion (agent), or user_turn / span (caller)
start_ms, end_msintegerAudio 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"
}
FieldTypeDescription
voice.idstringCustom voice public id
voice.namestringAgent voice value in the form custom:<public_id>
voice.display_namestringOrganization-facing voice name
voice.languagestringThe clone's single language code
voice.genderstringmale, female, or an empty string
voice.statusstringready for voice.ready; failed for voice.failed
voice.failure_reasonstringEmpty on success; processing failure detail on failure
voice.created_at, voice.updated_attimestampISO 8601 timestamps
reasonstringFailure 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"
}
FieldTypeDescription
grade.idintegerGrade id
grade.scoreinteger | null0–100
grade.call_outcomestringsuccess, failure, unknown, or no_conversation
grade.summarystringOne-paragraph summary
grade.detected_issuesarrayIssue strings found by the grader
grade.statusstringAlways completed — only finished runs emit
grade.grader_modelstringWhich grader produced the result (e.g. heuristic-v1)
grade.graded_at, grade.created_attimestamp

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"
}
FieldTypeDescription
issue_report.severitystringcritical, warning, or info
issue_report.statusstringopen or resolved
issue_report.sourcestringuser (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"
}
FieldTypeDescription
test_call_run.target_typestringagent or phone_number
test_call_run.target_idintegerThe agent id or phone-number id the run targeted, matching target_type
test_call_run.statusstringcompleted or failed
test_call_run.call_idinteger | nullnull when the run failed before a call was placed
test_call_run.error_messagestringEmpty 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"
}
FieldTypeDescription
event_id (in data)UUIDThe alert firing id — distinct from the envelope's delivery event_id
rule_id, rule_nameUUID, stringThe rule that fired
metricstringsuccess_rate, failure_rate, avg_score, call_volume, or suite_regression
comparatorstringlt, lte, gt, or gte
metric_valuenumberThe metric's value over the window when the rule fired
thresholdnumberThe configured threshold
window_hoursintegerTrailing evaluation window
fired_attimestamp

See the Alerts guide for creating rules, metrics, cooldowns, and the email / Slack channels.