> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.telekesher.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.telekesher.dev/_mcp/server.

# Call events

Call lifecycle events track the creation, answering, and termination of voice sessions. These are the foundational events your application will handle. Phone sessions begin with `call.created` (inbound) or `command.call.dial.accepted` (outbound); standalone WebSocket sessions have a separate startup sequence described under [Call Lifecycle](#call-lifecycle).

All webhook deliveries are signed. See [Signature Verification](/webhooks/overview#signature-verification) for how to validate event authenticity.

## call.created

Fired automatically when a new inbound phone, SIP, or WebRTC call arrives and a session is created. Outbound calls begin with [`command.call.dial.accepted`](#commandcalldialaccepted), while standalone WebSocket sessions begin with [`websocket.connected`](/webhooks/websocket-events) or [`websocket.failed`](/webhooks/websocket-events#websocketfailed).

**Payload schema**

| Field          | Type   | Description                                                                                                                                                                                                |
| -------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`        | string | Always `"call.created"`. **Required**                                                                                                                                                                      |
| `session_uuid` | string | Unique session identifier. **Required**                                                                                                                                                                    |
| `caller_id`    | string | Caller ID number of the incoming call, in canonical international E.164 format with leading `+` (e.g. `+972527121102`). **Required**                                                                       |
| `did`          | string | Called DID (Direct Inward Dialing) number, in canonical E.164 format with leading `+` (e.g. `+972747713001`). **Required**                                                                                 |
| `type`         | string | How the inbound session originated. One of: `phone_in`, `sip`, `webrtc`, `unknown`. **Required**                                                                                                           |
| `timestamp`    | string | RFC 3339 / ISO 8601 UTC timestamp at which the event was emitted (e.g. `"2025-06-06T08:53:20.000Z"`). Always `Z` suffix, always 3-digit millisecond fraction. Present on every webhook event. **Required** |

```json
{
  "event": "call.created",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "caller_id": "+972527121102",
  "did": "+972747713001",
  "type": "phone_in",
  "timestamp": "2025-06-06T08:53:20.000Z"
}
```

## call.answered

Fired exactly once when the call is answered. In terms of [SIP response
codes](https://en.wikipedia.org/wiki/List_of_SIP_response_codes), this event
corresponds strictly to a `200 OK` from the called party — `183 Session Progress`
is reported separately as [`call.early_media_started`](#callearly_media_started)
and is **not** an answer.

When you issue an explicit [`answer`](/api/sessions/answer) command, a SIP 200 OK emits one [`call.answered`](#callanswered) event. An unanswered call emits [`call.ended`](#callended) with `answered: false`. The [`call.answered`](#callanswered) payload omits `operation_uuid` and `success`.

**Payload schema**

| Field          | Type   | Description                                                                                                    |
| -------------- | ------ | -------------------------------------------------------------------------------------------------------------- |
| `event`        | string | Always `"call.answered"`. **Required**                                                                         |
| `session_uuid` | string | Unique session identifier. **Required**                                                                        |
| `timestamp`    | string | RFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. **Required** |

```json
{
  "event": "call.answered",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "timestamp": "2025-06-06T08:53:22.000Z"
}
```

## call.ended

[`call.ended`](#callended) is the **single terminal event** for every session. It fires exactly once per session — whether or not the call was ever answered. Use the `answered` boolean to distinguish the two cases:

* **Post-answer** termination: `answered: true`, a real `duration_seconds`, and `hangup_by` identifying the party who ended the call.
* **Pre-answer** termination (dial never connected, rejected, or cancelled before answer): `answered: false`, `duration_seconds: 0`, and `hangup_by` identifying the responsible party where it can be attributed (e.g. `"system"` for a ring/dial timeout) or `"unknown"` when the originator is indeterminate.

It is also fired synthetically when a session becomes orphaned — i.e. it has had no media or signalling activity for approximately 120 seconds and is cleaned up by the platform (see [Timing Limits](#timing-limits)). Synthetic hangups carry `answered: false` and `duration_seconds: 0`.

**Payload schema**

| Field                | Type    | Description                                                                                                                                                                                                                                                                                                                                                                                                        |
| -------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `event`              | string  | Always `"call.ended"`. **Required**                                                                                                                                                                                                                                                                                                                                                                                |
| `session_uuid`       | string  | Unique session identifier. **Required**                                                                                                                                                                                                                                                                                                                                                                            |
| `answered`           | boolean | `true` if the call reached a true answer (SIP 200 OK) before ending; `false` for any pre-answer termination, including dial timeouts and synthetic orphan cleanup. **Required**                                                                                                                                                                                                                                    |
| `hangup_cause`       | string  | Hangup cause. One of: `completed`, `busy`, `rejected`, `timeout`, `not_answered`, `invalid_number`, `cancelled`, `carrier_congested`, `carrier_unreachable`, `failed`. Treat any unrecognized value as `failed` — the bucket vocabulary may gain values in future. `carrier_congested` is transient and retry-safe, while `carrier_unreachable` is the permanent/config bucket (see the table below). **Required** |
| `hangup_by`          | string  | Which party ended the call. One of: `caller`, `callee`, `system`, `network`, `unknown`. See the [Hangup By](#hangup-by) table below. **Required**                                                                                                                                                                                                                                                                  |
| `hangup_description` | string  | Human-readable English summary of why the call ended. Presentation only — the wording may change without notice, so branch on `hangup_cause` / `hangup_by`, never on this string. **Required**                                                                                                                                                                                                                     |
| `duration_seconds`   | integer | Call duration in seconds. `0` for pre-answer terminations and for synthetic hangups from orphan cleanup. **Required**                                                                                                                                                                                                                                                                                              |
| `timestamp`          | string  | RFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. **Required**                                                                                                                                                                                                                                                                                                     |

Example — answered:

```json
{
  "event": "call.ended",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "answered": true,
  "hangup_cause": "completed",
  "hangup_by": "caller",
  "hangup_description": "The caller hung up.",
  "duration_seconds": 45,
  "timestamp": "2025-06-06T08:54:07.000Z"
}
```

Example — pre-answer termination:

```json
{
  "event": "call.ended",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "answered": false,
  "hangup_cause": "cancelled",
  "hangup_by": "unknown",
  "hangup_description": "The caller cancelled before the call was answered.",
  "duration_seconds": 0,
  "timestamp": "2025-06-06T08:53:52.000Z"
}
```

### Hangup Causes

| Cause                 | Description                                                                                                                                                                                                                                                                          |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `completed`           | Normal call completion. The call ended gracefully.                                                                                                                                                                                                                                   |
| `busy`                | The called party was busy.                                                                                                                                                                                                                                                           |
| `rejected`            | The call was rejected by the called party.                                                                                                                                                                                                                                           |
| `timeout`             | Ring or media timeout — far end did not answer within the call's `ring_timeout_sec`, or media stopped flowing for too long.                                                                                                                                                          |
| `not_answered`        | The called party could not be reached — e.g. the subscriber is not registered, absent, or the number has been changed/rerouted. Note: an ordinary "rang but nobody answered" outcome is reported as `timeout`, not `not_answered`.                                                   |
| `invalid_number`      | The destination number was rejected as malformed or unallocated.                                                                                                                                                                                                                     |
| `cancelled`           | The originator cancelled the call before it was answered.                                                                                                                                                                                                                            |
| `carrier_congested`   | The carrier or an upstream switch is temporarily congested, or no channel was momentarily available. **Retry-safe:** this condition is transient, so the same call MAY succeed if you retry it — potentially over a different route. Safe to re-attempt after a short delay.         |
| `carrier_unreachable` | A permanent carrier- or configuration-side problem prevented the call from completing (the network is faulted, or the gateway/profile/route is misconfigured). **Not retry-safe:** retrying the same call is unlikely to help until the underlying carrier/config issue is resolved. |
| `failed`              | The call failed due to a system or network error not covered by the buckets above.                                                                                                                                                                                                   |

Treat any unrecognized `hangup_cause` value as `failed` — the bucket vocabulary may gain values in future.

### Hangup By

`hangup_by` identifies which party ended the call. It is orthogonal to `hangup_cause` (the *reason*): a single cause may originate from either side.

| Value     | Meaning                                                                                                                                       |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `caller`  | The party who placed the call disconnected. On an inbound call this is the external customer; on an outbound call this is your app's own leg. |
| `callee`  | The party who was called disconnected. On an inbound call this is your app's leg; on an outbound call this is the external destination.       |
| `system`  | The platform ended the call (ring/dial/media timeout, orphan cleanup, shutdown).                                                              |
| `network` | A carrier or SIP-intermediate failure ended the call.                                                                                         |
| `unknown` | The signal was unavailable.                                                                                                                   |

### Hangup Description

Human-readable English summary of why the call ended. Presentation only — the wording may change without notice, so branch on `hangup_cause` / `hangup_by`, never on this string.

## command.call.dial.accepted

Fired after a successful [POST /v1/sessions:dial](/api/sessions/dial) when the gateway begins dialing the destination. Outbound only.

**Payload schema**

| Field          | Type   | Description                                                                                            |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------ |
| `event`        | string | Always `"command.call.dial.accepted"`.                                                                 |
| `session_uuid` | string | Unique session identifier.                                                                             |
| `direction`    | string | Always `"outbound"`.                                                                                   |
| `type`         | string | Always `"phone_out"`.                                                                                  |
| `to`           | string | Destination number, in canonical international E.164 format with leading `+` (e.g. `+972527121102`).   |
| `from`         | string | Caller-ID presented to the far end, in canonical E.164 format with leading `+` (e.g. `+972747713001`). |
| `timestamp`    | string | RFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event.      |

```json
{
  "event": "command.call.dial.accepted",
  "session_uuid": "acW68-...",
  "direction": "outbound",
  "type": "phone_out",
  "to": "+972527121102",
  "from": "+972747713001",
  "timestamp": "2025-06-06T08:53:20.123Z"
}
```

## call.ringing\_started

Fired when the far end indicates ringing on an outbound dial.

**Payload schema**

| Field          | Type   | Description                                                                                                    |
| -------------- | ------ | -------------------------------------------------------------------------------------------------------------- |
| `event`        | string | Always `"call.ringing_started"`. **Required**                                                                  |
| `session_uuid` | string | Unique session identifier. **Required**                                                                        |
| `timestamp`    | string | RFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. **Required** |

```json
{
  "event": "call.ringing_started",
  "session_uuid": "acW68-...",
  "timestamp": "2025-06-06T08:53:21.123Z"
}
```

## call.early\_media\_started

Fired when the far end starts sending early media (audio before answer, e.g. ringback or carrier announcements). This corresponds to SIP 183 Session Progress.

Early media indicates that audio is flowing before the called party answers.
[`call.answered`](#callanswered) fires when the called party returns SIP 200 OK.

**Payload schema**

| Field          | Type   | Description                                                                                                    |
| -------------- | ------ | -------------------------------------------------------------------------------------------------------------- |
| `event`        | string | Always `"call.early_media_started"`. **Required**                                                              |
| `session_uuid` | string | Unique session identifier. **Required**                                                                        |
| `timestamp`    | string | RFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. **Required** |

```json
{
  "event": "call.early_media_started",
  "session_uuid": "acW68-...",
  "timestamp": "2025-06-06T08:53:22.123Z"
}
```

## Call Lifecycle

Every session moves through a sequence of lifecycle events and ends with **exactly one** terminal [`call.ended`](#callended), including unanswered calls (`answered: false`). The optional states (`?`) may or may not fire depending on far-end signalling.

```text
Outbound (POST /v1/sessions:dial):

  command.call.dial.accepted
      └─▶ call.ringing_started?
              └─▶ call.early_media_started?          (SIP 183 — NOT an answer)
                      ├─▶ call.answered ─▶ … ─▶ call.ended { answered: true }
                      └────────────────────▶ call.ended { answered: false }
                                              (timeout / busy / rejected / cancelled,
                                               duration_seconds: 0; hangup_by is "system"
                                               for a ring/dial timeout, else "unknown")

Inbound (incoming call):

  call.created
      └─▶ call.answered ─▶ … ─▶ call.ended { answered: true }
      └──────────────────────▶ call.ended { answered: false }   (caller gives up before answer)

Standalone WebSocket session (POST /v1/sessions:connectWebsocket):

  websocket.connected ─▶ … ─▶ websocket.disconnected ─▶ call.ended
       or
  websocket.failed ──────────────────▶ call.ended
       or
  connection timeout ─────────▶ call.ended
```

A standalone `type: "websocket"` session has no phone leg and emits neither `call.created` nor `command.call.dial.accepted`. [`websocket.connected`](/webhooks/websocket-events) advances it from `connecting` to `answered` without emitting `call.answered`. Its lifetime is bound to the backend WebSocket: a disconnect or connection failure ends the session, and `call.ended` follows the corresponding WebSocket event. See [Create Session](/api/sessions/connect-websocket) for the full contract.

**Ordering & dedup.** Delivery is at-least-once with in-order (FIFO) delivery per `session_uuid`: the delivery worker preserves emission order for events with the same `session_uuid`, including across retries, but the same event may be delivered more than once. Process those events in delivery order and dedupe on the delivery `idempotency_key`. Use `timestamp` as event-time metadata, not as a sorting key. Exactly one terminal [`call.ended`](#callended) is emitted per session.

### Outbound Call Origination Lifecycle

A [POST /v1/sessions:dial](/api/sessions/dial) produces the following outbound lifecycle events. Every event below carries a `timestamp` (RFC 3339 UTC string). For this session, preserve the delivery order guaranteed above.

| Order | Event                                                    | Fires                                                                                                        | Payload beyond `event` / `session_uuid` / `timestamp`                                                     |
| ----- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| 1     | [`command.call.dial.accepted`](#commandcalldialaccepted) | Always — immediately after the dial is accepted.                                                             | `direction` (`"outbound"`), `type` (`"phone_out"`), `to`, `from` (both canonical E.164 with leading `+`). |
| 2     | [`call.ringing_started`](#callringing_started)           | Optional — when the far end signals ringing.                                                                 | No additional payload fields.                                                                             |
| 3     | [`call.early_media_started`](#callearly_media_started)   | Optional — when SIP 183 early audio (ringback or announcements) flows.                                       | No additional payload fields.                                                                             |
| 4     | [`call.answered`](#callanswered)                         | Optional — exactly once, only on a true answer (SIP 200 OK). Skipped entirely if the call is never answered. | No additional payload fields.                                                                             |
| 5     | [`call.ended`](#callended)                               | Always — the single terminal event, answered or not.                                                         | `answered`, `hangup_cause`, `hangup_by`, `hangup_description`, `duration_seconds`.                        |

Each intermediate event is emitted at most once and only advances forward: a dial may skip straight from [`command.call.dial.accepted`](#commandcalldialaccepted) to [`call.ended`](#callended) (e.g. immediate rejection), or stop at any intermediate stage. [`call.early_media_started`](#callearly_media_started) reports SIP 183 early audio; [`call.answered`](#callanswered) reports a SIP 200 OK answer.

## Timing Limits

Three platform limits surface as ordinary [`call.ended`](#callended) events:

* **Outbound dial cap (`ring_timeout_sec`).** Every outbound dial supplies this value explicitly. If an outbound dial is not answered within that window, the platform stops the attempt and emits [`call.ended`](#callended) with `answered: false` and `hangup_cause: "timeout"`. This window is measured from when dialing begins (when [`command.call.dial.accepted`](#commandcalldialaccepted) is emitted), not from when the far end starts ringing — see [Ring timeout timing](/api/sessions/dial).
* **Bridge limit (\~120 seconds of inactivity).** A session that becomes orphaned (no media or signalling activity) for approximately 120 seconds is cleaned up and emits a synthetic [`call.ended`](#callended) with `answered: false` and `duration_seconds: 0`.
* **Standalone WebSocket connection cap.** A standalone `type: "websocket"` session whose backend never connects within the platform's connection window is ended and emits [`call.ended`](#callended).

These windows are platform behaviour and may change; treat the exact values as approximate and branch on `hangup_cause`, not on elapsed time you measure yourself.

## Answering Machine Detection (AMD)

The platform does not classify an outbound answer as human or machine. [`call.answered`](#callanswered) fires on every SIP 200 OK, including when a voicemail system picks up.

Lifecycle and interaction signals alone make voicemail detection necessarily **heuristic**. Common signals an application can combine:

* **Long uninterrupted single-party audio after answer.** Voicemail greetings are continuous monologues. If, after [`call.answered`](#callanswered), the far end produces a long stretch of audio with no DTMF (no [`dtmf.received`](/webhooks/dtmf-events)) and no natural pause where a person would respond to your prompt, treat it as a likely machine.
* **No interactive response to a prompt.** If you play a prompt and collect digits ([play\_and\_get\_digits](/api/collect-keypad-input/collect)) and consistently get [`digits.collected`](/webhooks/dtmf-events#digitscollected) with `status: "no_input"` or `"partial_then_timeout"`, the answering party may be a machine that cannot respond.
* **Early media that never becomes an answer.** A [`call.early_media_started`](#callearly_media_started) followed by a long delay and then a hangup with `answered: false` typically reflects ringback or a carrier announcement, not a person — this is the unanswered case, distinct from a machine that actually answers.

For reliable detection, analyze the call audio with a dedicated answering-machine-detection algorithm. You can supply it with real-time audio through a [WebSocket audio relay](/api/web-socket), or stream an active recording from its [`live_url`](/webhooks/recording-events#recordingbecame_available). Combine that analysis with lifecycle events and interaction signals as appropriate for your traffic. If the result indicates a recording, [delete the session](/api/sessions/delete), passing the event's `session_uuid` as `{uuid}`.

## Triggered By

These events are produced by the following endpoints and lifecycle triggers:

* [`call.created`](#callcreated) — automatic: fires on incoming calls (no API trigger).
* [`call.answered`](#callanswered) — automatic or [answer](/api/sessions/answer) command.
* [`call.ended`](#callended) — automatic (single terminal event, answered or not) or [session deletion](/api/sessions/delete).
* [`command.call.dial.accepted`](#commandcalldialaccepted) — [POST /v1/sessions:dial](/api/sessions/dial) (outbound only).
* [`call.ringing_started`](#callringing_started) — automatic on outbound ring indication.
* [`call.early_media_started`](#callearly_media_started) — automatic on SIP 183 progress (NOT an answer).