> For complete lifecycle-event payload schemas and examples, use the canonical overview: Session /api/sessions; Room lifecycle, participants, and room playback /api/rooms; Session playback /api/playback; Recording /api/recording; DTMF /api/keypad-input; Messages /api/messages; WebSocket Session /api/web-socket. Use /api/event-delivery for transport behavior. Endpoint pages name relevant events, but these overviews are the canonical references. # Session > Complete Session resource and lifecycle-event reference, including payload schemas and examples. A `Session` represents one addressable participant in a voice interaction. It can be a phone call or a WebSocket audio endpoint that your application can inspect and control. ## When to use Use a Session to represent and control one participant in a voice interaction, such as an inbound caller, an outbound call, or a WebSocket voice agent. A [Room](/api/rooms) connects multiple Sessions in a shared conversation. Playback, DTMF collection, recording, and WebSocket audio operate on an individual Session. SIP and WebRTC participants are planned for a future release. ## How Sessions are created The platform creates Sessions in these ways: | Origin | Session `type` | What your application receives | | ------------------------------------------------------------------------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | An incoming phone call | `phone_in` | The platform creates the Session and sends a [`session.created`](#sessioncreated) event. Your application can then answer, reject, or end it. | | [`POST /sessions:dial`](https://docs.vorbal.dev/api/sessions/dial) | `phone_out` | The API immediately returns a `session_uuid`. A [`session.created`](#sessioncreated) event identifies the outbound Session, followed by ringing, answer, and termination updates. Your application can cancel dialing before the callee answers. | | [`POST /sessions:connectWebsocket`](https://docs.vorbal.dev/api/web-socket/connect-websocket) | `websocket` | The API creates a media-only Session with no phone leg. It begins in `connecting` and becomes `answered` when the WebSocket connection is established. | | A configured SIP or WebRTC connection *(Future)* | `sip` or `webrtc` | In a future release, the Session will represent that connected voice participant and use the same Session APIs. | ## Session lifecycle The lifecycle field depends on the endpoint. Each item returned by [`GET /sessions`](https://docs.vorbal.dev/api/sessions/list) uses `state`, while `GET /sessions/{session_uuid}` uses `status`. Both use the same values: `new`, `connecting`, `ringing`, `early_media`, `answered`, or `ended`. Treat `state` and `status` as point-in-time snapshots for inspection and reconciliation. Use lifecycle events to react to changes. Every Session ends with exactly one terminal [`session.ended`](#sessionended) event, whether it was answered, rejected, timed out, disconnected, or explicitly deleted. ## Control a Session Use the `session_uuid` to: * Inspect one Session or list Sessions. * Answer an incoming call, reject it before answer with an allowed reason, cancel outbound dialing before the callee answers, or request unconditional termination. * Play audio and collect DTMF digits. * Create and control multiple Session recordings by `recording_uuid`. * Add the Session to a Room and control its participation. Most control commands are asynchronous. An accepted command returns `202 Accepted` with an `operation_uuid`. The reference for each command identifies the event that confirms completion or reports the resulting lifecycle change. ## Methods * [Originate an outbound call](https://docs.vorbal.dev/api/sessions/dial) * [Create a WebSocket Session](https://docs.vorbal.dev/api/web-socket/connect-websocket) * [List sessions](https://docs.vorbal.dev/api/sessions/list) * [Get session details](https://docs.vorbal.dev/api/sessions/get) * [Answer an incoming session](https://docs.vorbal.dev/api/sessions/answer) * Reject an unanswered incoming session with `POST /v1/sessions/{uuid}/reject` * Cancel outbound dialing with `POST /v1/sessions/{uuid}/cancel` * [Delete a session](https://docs.vorbal.dev/api/sessions/delete) To use Session lifecycle, Playback, or DTMF commands, pass the Session's `session_uuid` in the `{uuid}` path parameter. The Recording collection creates, lists, and controls the [Recordings](/api/recording) that the Session owns. Recording control commands also require the exact `recording_uuid`; there is no implicit current Recording. ### Outbound caller ID and trunk selection When you call `POST /v1/sessions:dial`, `from` must be an allowed caller ID for your application. You can include `caller_id_session_uuid` to use the caller ID from a live inbound phone Session owned by your application. That Session's caller ID must exactly match `from`. This option authorizes the caller ID for that dial only. You can include `trunk_id` to request a specific eligible outbound route. If you omit it, the platform matches the caller ID and destination against each enabled route, then selects the lowest-priced route with available capacity. The platform uses the longest matching caller ID prefix and destination rate prefix independently within each route. When a route is saturated, an unpinned dial can use the next eligible route. A pinned route must support the destination and caller ID and does not fall back to another route. If no eligible route has a matching caller ID prefix and destination price, the request returns `503 unavailable`. If eligible routes are all saturated or the application's concurrent setup limit is full, it returns HTTP `529` with code `unavailable`, message `overloaded`, and a `Retry-After: 5` header. These capacity responses happen before monthly dial quota admission. A `202 Accepted` response confirms Session admission, not that the outbound call attempt has started or that the destination is ringing. The platform starts call attempts asynchronously and enforces each route's maximum new setup attempts in a rolling one-second window. Each started attempt counts, even if it fails or receives no response. Your application's concurrent setup limit applies across all outbound routes. Pending and ringing Sessions count until answer, confirmed termination, or a definitive no-send outcome; the default is one, and zero means unlimited. Check the effective value in `GET /api/v1/app`. When that application limit is full, the platform returns `529` immediately rather than waiting for a slot. When trunk and application capacity remain, a new attempt can start while an earlier one is still waiting for a response. Use Session events to follow call progress and completion. After dispatch, the platform does not retry or switch routes. When no outbound catalog exists, the platform initializes an initial HOT route without a manual catalog write. That route permits only its configured caller ID prefixes; a dial with another caller ID remains unavailable unless an operator adds another eligible route. Additional providers and their rates remain operator-configured. An existing catalog, including an empty one, is preserved rather than replaced with a default route. ## Resource representation ### The Session resource ```json { "caller_id": "string", "created_at": "2023-01-01T00:00:00Z", "did": "string", "session_uuid": "string", "state": "new", "type": "phone_in" } ``` ## Properties ### Schema (`Session`) ```yaml components: schemas: SessionState: type: string enum: - new - connecting - ringing - early_media - answered - ended description: >- Current lifecycle state of the session. `connecting` applies to a `websocket` session whose WebSocket server connection has not yet been established. title: SessionState SessionType: type: string enum: - phone_in - phone_out - sip - websocket - unknown description: >- The nature of the session: - `phone_in`: inbound phone call from a carrier - `phone_out`: outbound phone call placed with `dial` - `sip`: a registered SIP device - `websocket`: a WebSocket session with no phone leg - `unknown`: could not be determined or is not a supported public session type title: SessionType Session: type: object properties: caller_id: type: string description: >- Caller ID in canonical international E.164 format with leading `+` (e.g. `+972500000000`). Empty for `websocket` sessions (no phone leg). created_at: type: string format: date-time description: |- When the session was created, as an RFC 3339 / ISO 8601 UTC string (e.g. "2025-05-06T12:41:36.000Z"). Storage resolution is one second, so the millisecond fraction is always `.000`. Empty string if the originating timestamp is missing or unparseable. did: type: string description: >- Called DID in canonical E.164 format with leading `+` (e.g. `+972740000000`). Empty for `websocket` sessions (no phone leg). session_uuid: type: string description: Session identifier. state: $ref: '#/components/schemas/SessionState' description: >- Current lifecycle state of the session. `connecting` applies to a `websocket` session whose WebSocket server connection has not yet been established. type: $ref: '#/components/schemas/SessionType' description: >- The nature of the session: - `phone_in`: inbound phone call from a carrier - `phone_out`: outbound phone call placed with `dial` - `sip`: a registered SIP device - `websocket`: a WebSocket session with no phone leg - `unknown`: could not be determined or is not a supported public session type required: - created_at - session_uuid - state - type title: Session ``` ## Lifecycle events Session lifecycle events track the creation, answering, and termination of voice sessions. The first lifecycle signal depends on the session type: * Inbound phone sessions begin with `session.created`. * For an outbound call, the dial request's `202 Accepted` response confirms admission and `session.created` identifies the admitted Session as `phone_out`. * WebSocket Sessions follow the separate startup sequence under [Session lifecycle](#session-lifecycle). > **Note** > > All webhook deliveries are signed. See [Signature Verification](/api/event-delivery#signature-verification) for how to validate event authenticity. Every payload schema below also includes the required `aud` string, which identifies the application that should receive the signed event. ## session.created Fired automatically when an inbound phone, SIP, or WebRTC call arrives, or when an outbound phone call creates a Session. WebSocket Sessions begin with [`websocket.connected`](/api/web-socket#websocketconnected) or [`websocket.failed`](/api/web-socket#websocketfailed) instead. **Payload schema** | Field | Type | Description | | -------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"session.created"`. **Required** | | `session_uuid` | string | Unique session identifier. **Required** | | `caller_id` | string | Caller ID number in canonical international E.164 format with leading `+` (e.g. `+972500000000`). **Required** | | `did` | string | Called destination number in canonical E.164 format with leading `+` (e.g. `+972740000000`). **Required** | | `type` | string | How the phone Session originated. One of: `phone_in`, `phone_out`, `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 { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "session.created", "session_uuid": "00000000-0000-4000-8000-000000000101", "caller_id": "+972500000000", "did": "+972740000000", "type": "phone_in", "timestamp": "2025-06-06T08:53:20.000Z" } ``` ## session.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 [`session.early_media_started`](#sessionearly_media_started) and is **not** an answer. When you issue an explicit [`answer`](/api/sessions/answer) command, a SIP 200 OK emits one [`session.answered`](#sessionanswered) event. An unanswered call emits [`session.ended`](#sessionended) with `answered: false`. The event never includes a `success` field. It includes `operation_uuid` only when the platform can uniquely associate the lifecycle transition with one pending call-control command. If present, use it for correlation; do not treat it as proof that the command alone caused the answer. **Payload schema** | Field | Type | Description | | ---------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"session.answered"`. **Required** | | `session_uuid` | string | Unique session identifier. **Required** | | `operation_uuid` | string | Correlation ID from an accepted call-control command. Present only when the platform can uniquely associate this event with one pending command; it does not prove that the command alone caused the answer. **Optional** | | `timestamp` | string | RFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "session.answered", "session_uuid": "00000000-0000-4000-8000-000000000101", "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", "timestamp": "2025-06-06T08:53:22.000Z" } ``` ## session.ended [`session.ended`](#sessionended) 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 session. * **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. The platform also emits this event synthetically when it cleans up an orphaned session after approximately 120 seconds without media or signalling activity (see [Timing limits](#timing-limits)). Synthetic hangups carry `answered: false` and `duration_seconds: 0`. The event includes `operation_uuid` only when the platform can uniquely associate the termination with one pending call-control command. If the association is unavailable or ambiguous, the field is omitted. When present, use it for correlation; do not treat it as proof that the command alone caused the termination. **Payload schema** | Field | Type | Description | | -------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `event` | string | Always `"session.ended"`. **Required** | | `session_uuid` | string | Unique session identifier. **Required** | | `operation_uuid` | string | Correlation ID from an accepted call-control command. Present only when the platform can uniquely associate this event with one pending command; it does not prove that the command alone caused the termination. **Optional** | | `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 session. 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 { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "session.ended", "session_uuid": "00000000-0000-4000-8000-000000000101", "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 { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "session.ended", "session_uuid": "00000000-0000-4000-8000-000000000101", "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 provider or routing configuration is invalid). **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 session. 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 session. | | `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. ## session.ringing_started Fired when the far end indicates ringing on an outbound dial. **Payload schema** | Field | Type | Description | | -------------- | ------ | -------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"session.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 { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "session.ringing_started", "session_uuid": "00000000-0000-4000-8000-000000000101", "timestamp": "2025-06-06T08:53:21.123Z" } ``` ## session.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. [`session.answered`](#sessionanswered) fires when the called party returns SIP 200 OK. **Payload schema** | Field | Type | Description | | -------------- | ------ | -------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"session.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 { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "session.early_media_started", "session_uuid": "00000000-0000-4000-8000-000000000101", "timestamp": "2025-06-06T08:53:22.123Z" } ``` ## Lifecycle sequences Every session moves through a sequence of lifecycle events and ends with **exactly one** terminal [`session.ended`](#sessionended), including unanswered calls (`answered: false`). The optional states (`?`) may or may not fire depending on far-end signalling. ```text Outbound (POST /v1/sessions:dial): 202 Accepted └─▶ session.created { type: "phone_out" } └─▶ session.ringing_started? └─▶ session.early_media_started? (SIP 183 — NOT an answer) ├─▶ session.answered ─▶ … ─▶ session.ended { answered: true } └────────────────────▶ session.ended { answered: false } (timeout / busy / rejected / cancelled, duration_seconds: 0; hangup_by is "system" for a ring/dial timeout, else "unknown") Inbound (incoming call): session.created └─▶ session.answered ─▶ … ─▶ session.ended { answered: true } └──────────────────────▶ session.ended { answered: false } (caller gives up before answer) WebSocket Session (POST /v1/sessions:connectWebsocket): websocket.connected ─▶ … ─▶ websocket.disconnected ─▶ session.ended or websocket.failed ──────────────────▶ session.ended or connection timeout ─────────▶ session.ended ``` A `type: "websocket"` Session has no phone leg and does not emit `session.created`. [`websocket.connected`](/api/web-socket#websocketconnected) advances it from `connecting` to `answered` without emitting `session.answered`. The Session stays active while the WebSocket connection is open. If the connection fails or closes, the Session ends. A `session.ended` event follows the corresponding WebSocket event. See [Create Session](/api/sessions/connect-websocket) for the full contract. > **Tip** > > **Ordering & dedup.** 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 using the verified `Webhook-Id`. Use `timestamp` as event-time metadata, not as a sorting key. Exactly one terminal [`session.ended`](#sessionended) 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 | `202 Accepted` response | Always — immediately after the dial is admitted. | `session_uuid`, `status`. | | 2 | [`session.created`](#sessioncreated) | Always — identifies the admitted outbound Session. | `caller_id`, `did`, `type: "phone_out"`. | | 3 | [`session.ringing_started`](#sessionringing_started) | Optional — when the far end signals ringing. | No additional payload fields. | | 4 | [`session.early_media_started`](#sessionearly_media_started) | Optional — when SIP 183 early audio (ringback or announcements) flows. | No additional payload fields. | | 5 | [`session.answered`](#sessionanswered) | Optional — exactly once, only on a true answer (SIP 200 OK). Skipped entirely if the call is never answered. | No additional payload fields. | | 6 | [`session.ended`](#sessionended) | 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. After `session.created`, a dial may skip straight to [`session.ended`](#sessionended) (for example, after an immediate rejection), or stop at any intermediate stage. [`session.early_media_started`](#sessionearly_media_started) reports SIP 183 early audio; [`session.answered`](#sessionanswered) reports a SIP 200 OK answer. ## Timing limits Three platform limits surface as ordinary [`session.ended`](#sessionended) 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 [`session.ended`](#sessionended) with `answered: false` and `hangup_cause: "timeout"`. This window is measured from when dialing begins (when the dial request returns `202 Accepted`), 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 [`session.ended`](#sessionended) with `answered: false` and `duration_seconds: 0`. * **WebSocket connection cap.** A `type: "websocket"` Session whose WebSocket server never connects within the platform's connection window is ended and emits [`session.ended`](#sessionended). 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) > **Warning** > > The platform does not classify an outbound answer as human or machine. [`session.answered`](#sessionanswered) fires on every SIP 200 OK, including when a voicemail system picks up. The platform can't tell you whether a human or a machine answered, so you must infer it from lifecycle and interaction signals. That makes voicemail detection necessarily **heuristic**. Common signals an application can combine: * **Long uninterrupted single-party audio after answer.** Voicemail greetings are continuous monologues. If, after [`session.answered`](#sessionanswered), the far end produces a long stretch of audio with no DTMF (no [`dtmf.received`](/api/keypad-input#dtmfreceived)) 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 use [DTMF collection](/api/keypad-input/collect) and consistently get [`digits.collected`](/api/keypad-input#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 [`session.early_media_started`](#sessionearly_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. Stream an active recording from its [`live_url`](/api/recording#recordingbecame_available), and combine that analysis with lifecycle events and interaction signals appropriate for your traffic. If the result indicates a recording, [delete the session](/api/sessions/delete) and pass the event's `session_uuid` as `{uuid}`. ## Triggered by These events are produced by the following endpoints and lifecycle triggers: * [`session.created`](#sessioncreated) — automatic: fires on incoming calls and outbound calls created by [`POST /sessions:dial`](https://docs.vorbal.dev/api/sessions/dial). * [`session.answered`](#sessionanswered) — automatic or [answer](/api/sessions/answer) command. * [`session.ended`](#sessionended) — automatic (single terminal event, answered or not) or [session deletion](/api/sessions/delete). * [`session.ringing_started`](#sessionringing_started) — automatic on outbound ring indication. * [`session.early_media_started`](#sessionearly_media_started) — automatic on SIP 183 progress (NOT an answer). > Build reliable calling experiences with Vorbal APIs for calls, media, messaging, and webhooks. ## API Docs - Sessions & Calls [List sessions](https://docs.vorbal.dev/api/sessions/list.md) - Sessions & Calls [Get session details](https://docs.vorbal.dev/api/sessions/get.md) - Sessions & Calls [Originate outbound call](https://docs.vorbal.dev/api/sessions/dial.md) - Sessions & Calls [Answer a call](https://docs.vorbal.dev/api/sessions/answer.md) - Sessions & Calls [Reject an incoming call](https://docs.vorbal.dev/api/sessions/reject.md) - Sessions & Calls [Cancel an unanswered outgoing call](https://docs.vorbal.dev/api/sessions/cancel.md) - Sessions & Calls [Delete session](https://docs.vorbal.dev/api/sessions/delete.md) ## OpenAPI Specification The raw OpenAPI 3.1 specification for this API is available at: - [OpenAPI JSON](https://docs.vorbal.dev/openapi.json) - [OpenAPI YAML](https://docs.vorbal.dev/openapi.yaml)