> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.getdial.ai/api-reference/events/call-status-changed/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getdial.ai/_mcp/server. # call.status_changed > Event emitted each time a call moves through its lifecycle — queued, ringing, in-progress, terminated. Emitted every time a voice call on one of your Dial numbers advances to a new lifecycle status. It's the live, thin counterpart to [`call.ended`](/api-reference/events/call-ended): `call.status_changed` streams each transition as it happens (a call started ringing, was answered, terminated), while `call.ended` is a one-shot terminal snapshot carrying outcome details like `durationSeconds`. Wait on `call.status_changed` to react the moment a call starts or is answered; keep waiting on `call.ended` if you only care that it finished. The `status` payload mirrors the `status` object returned by `GET /api/v1/calls/{id}` — the `relatedObject.url` — so what you see on the stream always matches what a fetch of the call record would show. ## Lifecycle states A call moves forward through these states, never backward: `Queued` → `Ringing` → `In-Progress` → `Terminated` Not every call passes through every state, so don't build a consumer that requires all four events: an outbound call that fails to connect can jump from `Queued` straight to `Terminated`, and some inbound calls first become visible to Dial once they're already answered — their first event is `In-Progress`. Every call's **last** event is always `Terminated`. ## Schema Shares the common [event envelope](/api-reference/events/overview#envelope) (`id`, `object`, `type`, `version`, `createdAt`, `relatedObject`). `relatedObject` is `{ id: , type: "call", url: "/api/v1/calls/" }`. The envelope's `createdAt` is when the transition happened. The `data` payload: | `data` field | Type | Description | | ----------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `callId` | string | Dial-issued ID for the call. Matches the `id` returned by `GET /api/v1/calls`. | | `direction` | string | `"inbound"` if someone called your Dial number; `"outbound"` if the call originated from a `dial call` / `POST /api/v1/calls`. | | `from` | string | Calling phone number, E.164. | | `to` | string | Called phone number, E.164. | | `phoneNumberId` | string | ID of the Dial number this call is on — matches `GET /api/v1/numbers`. Use it to filter the stream to one number. | | `status` | object | The status the call just entered: `{ state, label }`. `state` is one of `"Queued"`, `"Ringing"`, `"In-Progress"`, `"Terminated"`; `label` is the display string (e.g. `"Ringing"`, `"Completed"`, `"Completed (Cancelled)"`). | | `previousState` | string \| null | The state the call left. `null` on the call's first event (nothing preceded it). | | `terminationType` | string \| null | Only set when `status.state` is `"Terminated"`: `completed`, `busy`, `no-answer`, `failed`, or `canceled` — the same value `call.ended` reports as `status`. `null` for all non-terminal transitions. | | `failureReason` | string \| null | Only set when `terminationType` is `failed` and Dial knows why: `self_hosted_key_rejected`, `self_hosted_agent_not_found`, `self_hosted_at_capacity`, or `self_hosted_unreachable`. `null` otherwise. Treat an unknown value like `null`. | ## Example An inbound call being answered: ```json { "id": "evt_5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b", "object": "event", "type": "call.status_changed", "version": 1, "createdAt": "2026-05-28T14:34:49.771Z", "relatedObject": { "id": "call_01HW3X7P5QF6K2YQ9YJ7Q8R5N3", "type": "call", "url": "/api/v1/calls/call_01HW3X7P5QF6K2YQ9YJ7Q8R5N3" }, "data": { "callId": "call_01HW3X7P5QF6K2YQ9YJ7Q8R5N3", "direction": "inbound", "from": "+14155550123", "to": "+14155559876", "phoneNumberId": "clx8k2j9h0001mk08w3t5r7qz", "status": { "state": "In-Progress", "label": "In-Progress" }, "previousState": "Ringing", "terminationType": null, "failureReason": null } } ``` ## When it fires * Once per state a call actually enters, inbound or outbound — at most four events per call, always ending with `Terminated`. Deduplicate on the envelope `id`; a given call emits each state at most once. * **Requesting a cancel does not emit an event** — cancellation is a request, not a state (see [Stop a call](/api-reference/calls/stop-call)). When the cancelled call actually ends, the `Terminated` event carries `terminationType: "canceled"` (or, for an answered call you hung up, `terminationType: "completed"` with `label: "Completed (Cancelled)"`). * The `Terminated` transition and [`call.ended`](/api-reference/events/call-ended) describe the same moment and both fire. They're intentionally redundant: `call.status_changed` keeps the lifecycle stream uniform, `call.ended` carries the terminal extras (`durationSeconds`, `canceled`, `transcriptAvailable`). Consume whichever fits, or both — they have distinct event `id`s. * To catch inbound calls the moment they arrive, filter on `direction: "inbound"` and treat the first event you see for a `callId` as the arrival — that's `Ringing` when Dial observes the ring, otherwise `In-Progress`. > Event emitted each time a call moves through its lifecycle — queued, ringing, in-progress, terminated.