Event reference
The body of every webhook event, the call record it carries, and how to handle ordering.
Every event is a JSON POST to your endpoint. See Webhooks for signing, retries and the delivery log.
Call events
call_started, call_ended and call_analyzed share one body:
{
"event": "call_ended",
"call_id": "6f1d0c2e-0b8a-4c7e-9d58-2a1e3f4b5c6d",
"agent_id": "46fd9050-816b-452c-b590-68d19e6e7340",
"call": { }
}call is the full call record as it was when the event was sent.
| Event | Notes |
|---|---|
call_started | The call connected. |
call_ended | Sent as soon as the call ends. analysis is still null. |
call_analyzed | Sent after call_ended, once analysis has run, and only for calls that produced a transcript. analysis is filled in. |
Transcript updates
transcript_updated is sent after each turn while the call is live. It adds three fields:
{
"event": "transcript_updated",
"call_id": "…",
"agent_id": "…",
"call": { },
"role": "user",
"turn_index": 4,
"transcript": [
{ "role": "agent", "text": "Thanks for calling.", "timestamp": "2026-11-07T22:15:40.425782" },
{ "role": "user", "text": "I'd like to book a cleaning.", "timestamp": "2026-11-07T22:15:44.100000" }
]
}roleis who spoke the turn that triggered this event.turn_indexgoes up by one per event within a call. Keep the highest one you have seen.transcriptis the whole transcript so far, so a delivery you missed or received out of order still leaves you correct.
This event is high volume. Subscribe only if you need live text. It is not sent for an agent that has transcript storage turned off.
Transfer events
transfer_started, transfer_bridged, transfer_cancelled and transfer_ended add a transfer object. Fields appear only when known.
{ "event": "transfer_started", "call_id": "…", "agent_id": "…", "call": { },
"transfer": { "target": "+14155550100", "mode": "warm" } }| Field | Meaning |
|---|---|
target | The number the call is going to. |
mode | cold or warm. |
reason | Why a transfer was cancelled: a carrier status such as busy or no-answer, or a failure reason. |
| Event | When |
|---|---|
transfer_started | The agent asked for the call to be transferred. |
transfer_bridged | The other person answered and was connected. For a cold transfer this arrives when the transferred leg finishes, followed at once by transfer_ended. |
transfer_cancelled | The transfer did not connect: busy, no answer, failed, or could not be placed. |
transfer_ended | A connected transfer finished. |
Chat events
chat_started, chat_ended and chat_analyzed carry chat_id and chat (the full chat with its messages) instead of call_id and call.
Campaign events
campaign_started, campaign_paused, campaign_resumed, campaign_completed and campaign_cancelled go to webhook endpoints only, and carry a campaign object instead of call.
The call record
The same object appears in these events and in the API's call responses.
| Field | Type | Meaning |
|---|---|---|
id | string | The call's id. |
agent_id, agent_name | string | The agent. agent_id is empty if the call had none. |
agent_version | number | The version the call ran. 0 is the live draft. |
status | string | active, queued, completed or failed. |
call_status | string | The same state as ongoing, registered, ended or error. |
call_type | string | phone_call or web_call. |
direction | string | inbound or outbound. |
metadata | object | Your data, set when the call was created or later. |
dynamic_variables | object | The variables the call started with. |
started_at, ended_at | string | UTC, ISO 8601 with no timezone suffix. ended_at is null while ongoing. |
start_timestamp, end_timestamp | number | Epoch milliseconds. |
duration_s, duration_ms | number | Length of the call. |
from_number, to_number, phone_number | string | The numbers involved. |
call_sid | string | The carrier's call id, or empty. |
transcript | array | One entry per turn: role (user or agent), text, timestamp. Empty when transcript storage is off, or after retention has purged it. |
transcript_text | string | The same transcript as Agent: … and User: … lines. |
recording_url | string or null | A short-lived signed link. Download promptly instead of storing it. |
disconnection_reason | string or null | Why it ended. See below. |
in_voicemail | boolean | Whether it reached a machine. |
analysis | object or null | Post-call analysis. See below. |
call_cost | object or null | Set when the call is finalized. See below. |
end_to_end_latency_ms | number or null | Average time from the caller finishing to the agent's first audio. |
latency | object or null | Per-stage percentiles (e2e, llm, tts, endpointing), each with p50, p90, p95, p99, min, max, num. |
Disconnection reasons
user_hangup, agent_hangup, call_transfer, voicemail_reached, inactivity, max_duration_reached, manual_stopped, dial_no_answer, flow_dead_end, flow_loop_guard and error. Treat the list as open: new reasons can be added, so handle an unknown value.
Analysis
| Field | Meaning |
|---|---|
call_summary | One to three sentences. |
user_sentiment | Positive, Neutral or Negative. |
call_successful | true or false. |
variables | Values for the fields you defined in post_call_analysis_data, keyed by name. |
model, extracted_at | The model used, and when. |
error | Set if extraction failed. variables may then be partial. |
Cost
Amounts are integers in units of $0.0001, so combined_cost: 1700 is $0.17.
| Field | Meaning |
|---|---|
combined_cost | The total for the call. |
total_duration_seconds | Billed length. |
total_duration_unit_price | The combined per-minute rate. |
product_costs | One entry per part of the price, each with product (such as platform), unit_price and cost. |
See Billing and pricing for what the parts are.
Handling events well
- Verify first. Check the signature against the raw body before you parse anything.
- Deduplicate. The same delivery can arrive more than once. Use
X-Dialgem-Delivery. - Expect disorder.
call_analyzedcan be followed by a late retry ofcall_ended. Compare ids and timestamps rather than arrival order. - Answer quickly. Return
2xxonce the event is stored and work after.