Session

A participant in a voice interaction, from creation through termination.
View as Markdown

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 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:

OriginSession typeWhat your application receives
An incoming phone callphone_inThe platform creates the Session and sends a session.created event. Your application can then answer, reject, or end it.
POST /sessions:dialphone_outThe API immediately returns a session_uuid. Ringing, answer, and termination updates arrive through Session lifecycle events. Your application can cancel dialing before the callee answers.
POST /sessions:connectWebsocketwebsocketThe 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 webrtcIn 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 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 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 the Sessions owned by your application.
  • 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 membership.

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

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 that the Session owns. Recording control commands also require the exact recording_uuid; there is no implicit current Recording.

Resource representation

The Session resource
1{
2 "caller_id": "string",
3 "created_at": "2023-01-01T00:00:00Z",
4 "did": "string",
5 "session_uuid": "string",
6 "state": "new",
7 "type": "phone_in"
8}

Properties

created_atdatetimeRequired
session_uuidstringRequired
Session identifier.
stateenumRequired
typeenumRequired
caller_idstringOptional
didstringOptional

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.
  • WebSocket Sessions follow the separate startup sequence under Session lifecycle.

All webhook deliveries are signed. See 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 a new inbound phone, SIP, or WebRTC call arrives and a session is created. The dial request’s 202 Accepted response admits the outbound call, while WebSocket Sessions begin with websocket.connected or websocket.failed.

Payload schema

FieldTypeDescription
eventstringAlways "session.created". Required
session_uuidstringUnique session identifier. Required
caller_idstringCaller ID number of the incoming call, in canonical international E.164 format with leading + (e.g. +972500000000). Required
didstringCalled DID (Direct Inward Dialing) number, in canonical E.164 format with leading + (e.g. +972740000000). Required
typestringHow the inbound session originated. One of: phone_in, sip, webrtc, unknown. Required
timestampstringRFC 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
1{
2 "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
3 "event": "session.created",
4 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
5 "caller_id": "+972500000000",
6 "did": "+972740000000",
7 "type": "phone_in",
8 "timestamp": "2025-06-06T08:53:20.000Z"
9}

session.answered

Fired exactly once when the call is answered. In terms 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 and is not an answer.

When you issue an explicit answer command, a SIP 200 OK emits one session.answered event. An unanswered call emits session.ended 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

FieldTypeDescription
eventstringAlways "session.answered". Required
session_uuidstringUnique session identifier. Required
operation_uuidstringCorrelation 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
timestampstringRFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. Required
1{
2 "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
3 "event": "session.answered",
4 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
5 "operation_uuid": "op_3b35df74",
6 "timestamp": "2025-06-06T08:53:22.000Z"
7}

session.ended

session.ended 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). 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

FieldTypeDescription
eventstringAlways "session.ended". Required
session_uuidstringUnique session identifier. Required
operation_uuidstringCorrelation 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
answeredbooleantrue 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_causestringHangup 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_bystringWhich party ended the session. One of: caller, callee, system, network, unknown. See the Hangup By table below. Required
hangup_descriptionstringHuman-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_secondsintegerCall duration in seconds. 0 for pre-answer terminations and for synthetic hangups from orphan cleanup. Required
timestampstringRFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. Required

Example — answered:

1{
2 "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
3 "event": "session.ended",
4 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
5 "answered": true,
6 "hangup_cause": "completed",
7 "hangup_by": "caller",
8 "hangup_description": "The caller hung up.",
9 "duration_seconds": 45,
10 "timestamp": "2025-06-06T08:54:07.000Z"
11}

Example — pre-answer termination:

1{
2 "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
3 "event": "session.ended",
4 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
5 "answered": false,
6 "hangup_cause": "cancelled",
7 "hangup_by": "unknown",
8 "hangup_description": "The caller cancelled before the call was answered.",
9 "duration_seconds": 0,
10 "timestamp": "2025-06-06T08:53:52.000Z"
11}

Hangup causes

CauseDescription
completedNormal call completion. The call ended gracefully.
busyThe called party was busy.
rejectedThe call was rejected by the called party.
timeoutRing or media timeout — far end did not answer within the call’s ring_timeout_sec, or media stopped flowing for too long.
not_answeredThe 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_numberThe destination number was rejected as malformed or unallocated.
cancelledThe originator cancelled the call before it was answered.
carrier_congestedThe 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_unreachableA 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.
failedThe 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.

ValueMeaning
callerThe 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.
calleeThe party who was called disconnected. On an inbound call this is your app’s leg; on an outbound call this is the external destination.
systemThe platform ended the call (ring/dial/media timeout, orphan cleanup, shutdown).
networkA carrier or SIP-intermediate failure ended the session.
unknownThe 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

FieldTypeDescription
eventstringAlways "session.ringing_started". Required
session_uuidstringUnique session identifier. Required
timestampstringRFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. Required
1{
2 "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
3 "event": "session.ringing_started",
4 "session_uuid": "acW68-...",
5 "timestamp": "2025-06-06T08:53:21.123Z"
6}

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 fires when the called party returns SIP 200 OK.

Payload schema

FieldTypeDescription
eventstringAlways "session.early_media_started". Required
session_uuidstringUnique session identifier. Required
timestampstringRFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. Required
1{
2 "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
3 "event": "session.early_media_started",
4 "session_uuid": "acW68-...",
5 "timestamp": "2025-06-06T08:53:22.123Z"
6}

Lifecycle sequences

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

Outbound (POST /v1/sessions:dial):
202 Accepted
└─▶ 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 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 for the full contract.

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 is emitted per session.

Outbound call origination lifecycle

A POST /v1/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.

OrderEventFiresPayload beyond event / session_uuid / timestamp
1202 Accepted responseAlways — immediately after the dial is admitted.session_uuid, status.
2session.ringing_startedOptional — when the far end signals ringing.No additional payload fields.
3session.early_media_startedOptional — when SIP 183 early audio (ringback or announcements) flows.No additional payload fields.
4session.answeredOptional — exactly once, only on a true answer (SIP 200 OK). Skipped entirely if the call is never answered.No additional payload fields.
5session.endedAlways — 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 the 202 response, a dial may skip straight to session.ended (e.g. immediate rejection), or stop at any intermediate stage. session.early_media_started reports SIP 183 early audio; session.answered reports a SIP 200 OK answer.

Timing limits

Three platform limits surface as ordinary session.ended 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 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.
  • 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 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.

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. session.answered 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, the far end produces a long stretch of audio with no DTMF (no dtmf.received) 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 and consistently get digits.collected 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 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, and combine that analysis with lifecycle events and interaction signals appropriate for your traffic. If the result indicates a recording, delete the session and pass the event’s session_uuid as {uuid}.

Triggered by

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