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.
All webhook deliveries are signed. See 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, while standalone WebSocket sessions begin with websocket.connected or websocket.failed.
Payload schema
call.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 call.early_media_started
and is not an answer.
When you issue an explicit answer command, a SIP 200 OK emits one call.answered event. An unanswered call emits call.ended with answered: false. The call.answered payload omits operation_uuid and success.
Payload schema
call.ended
call.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 realduration_seconds, andhangup_byidentifying the party who ended the call. - Pre-answer termination (dial never connected, rejected, or cancelled before answer):
answered: false,duration_seconds: 0, andhangup_byidentifying 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). Synthetic hangups carry answered: false and duration_seconds: 0.
Payload schema
Example — answered:
Example — pre-answer termination:
Hangup Causes
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.
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 when the gateway begins dialing the destination. Outbound only.
Payload schema
call.ringing_started
Fired when the far end indicates ringing on an outbound dial.
Payload schema
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 fires when the called party returns SIP 200 OK.
Payload schema
Call Lifecycle
Every session moves through a sequence of lifecycle events and ends with exactly one terminal call.ended, including unanswered calls (answered: false). The optional states (?) may or may not fire depending on far-end signalling.
A standalone type: "websocket" session has no phone leg and emits neither call.created nor command.call.dial.accepted. websocket.connected 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 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 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.
Each intermediate event is emitted at most once and only advances forward: a dial may skip straight from command.call.dial.accepted to call.ended (e.g. immediate rejection), or stop at any intermediate stage. call.early_media_started reports SIP 183 early audio; call.answered reports a SIP 200 OK answer.
Timing Limits
Three platform limits surface as ordinary call.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 emitscall.endedwithanswered: falseandhangup_cause: "timeout". This window is measured from when dialing begins (whencommand.call.dial.acceptedis emitted), 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
call.endedwithanswered: falseandduration_seconds: 0. - Standalone WebSocket connection cap. A standalone
type: "websocket"session whose backend never connects within the platform’s connection window is ended and emitscall.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. call.answered 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, the far end produces a long stretch of audio with no DTMF (nodtmf.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 play a prompt and collect digits (play_and_get_digits) and consistently get
digits.collectedwithstatus: "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_startedfollowed by a long delay and then a hangup withanswered: falsetypically 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, or stream an active recording from its live_url. Combine that analysis with lifecycle events and interaction signals as appropriate for your traffic. If the result indicates a recording, delete the session, passing the event’s session_uuid as {uuid}.
Triggered By
These events are produced by the following endpoints and lifecycle triggers:
call.created— automatic: fires on incoming calls (no API trigger).call.answered— automatic or answer command.call.ended— automatic (single terminal event, answered or not) or session deletion.command.call.dial.accepted— POST /v1/sessions:dial (outbound only).call.ringing_started— automatic on outbound ring indication.call.early_media_started— automatic on SIP 183 progress (NOT an answer).