Skip to content

Events & codes

Every code below is the same value everywhere. It appears on the event stream as data.reasonCode, on the Call object as reason_code, and on status-callback webhooks. One parser handles all three.

Pushed live over the event stream.

Code Event Summary
CALL.0100 call.initiated Call initiated
CALL.0180 call.ringing Ringing
CALL.0183 call.progress Call progress
CALL.0200 call.answered Answered
CALL.0300 call.completed Completed
CALL.0400 call.failed Failed
DIAL.0200 dial.answered Bridged leg answered
DIAL.0300 dial.completed Bridged leg ended
DIAL.0403 dial.blocked Dial destination blocked
PLAY.0400 play.url_refused Play URL refused
PLAY.0404 play.asset_missing Play asset not found
REC.0100 recording.started Recording started
REC.0300 recording.completed Recording completed
TTS.0200 tts.generated Speech generated
WH.0200 webhook.delivered Webhook delivered
WH.0400 webhook.failed Webhook failed
AUTH.0200 phone.authorized Phone authorized
AUTH.0401 phone.rejected Phone rejected
BILL.0300 wallet.debited Wallet debited
BILL.0310 wallet.low Wallet low
BILL.0402 wallet.insufficient Insufficient balance
BILL.0403 wallet.exhausted Balance exhausted mid-call
BILL.0407 account.suspended Account suspended
LIMIT.0429 call.concurrency_exceeded Concurrency cap reached
LIMIT.0430 call.cps_exceeded Calls-per-second limit exceeded
END.0403 call.no_did No DID to originate from
END.0488 call.cross_account Cross-account extension
END.0410 call.callee_offline Extension offline
END.0501 call.no_outbound_handler No outbound voice webhook
PLATFORM.0001 platform.concurrent Platform concurrent calls
PLATFORM.0503 platform.draining Platform paused for maintenance
SYS.0500 system.error System error

The terminal reason a call ended, or a request was refused. Each cause has its own code, so a refusal is never conflated with a different refusal.

Code HTTP Summary
BILL.0402 402 Insufficient balance
BILL.0403 402 Balance exhausted mid-call
BILL.0407 403 Account suspended
LIMIT.0429 429 Concurrency cap reached
LIMIT.0430 429 Calls-per-second limit exceeded
END.0200 200 Normal hangup
END.0404 404 Unallocated / inactive number
END.0408 408 Maximum duration reached
END.0480 480 No answer
END.0486 486 Busy
END.0487 487 Canceled before answer
END.0503 503 Ended by a platform safety cut
END.0504 504 Reconciled from the switch
END.0500 500 Carrier / network failure
END.0603 603 Rejected
END.0403 403 No DID to originate from
END.0488 488 Cross-account extension
END.0410 410 Extension offline
END.0501 501 No outbound voice webhook
PLATFORM.0503 503 Platform paused for maintenance

A call leg was created but is not yet ringing.

The remote party is being alerted (ringing).

Early media / “connecting…”: audio before answer, distinct from ringing.

The call was answered/bridged; the leg is connected and billing has started.

The call ended after connecting. Carries the terminal reasonCode + duration/cost.

The call did not complete (never connected, or was refused/cut). Carries the terminal reasonCode explaining exactly why.

A <Dial> bridged leg was answered by the far end.

A <Dial> bridged leg ended; carries its DialCallStatus + duration.

A <Dial> was blocked by the outbound destination allowlist, which is an anti-fraud control. The parent call continues, so this is an event on the dial attempt, not a call end.

A <Play> held text that is not a plain http or https URL, or a URL with characters that must be percent-encoded first, such as spaces or braces. The verb was skipped and the call continues with the next verb. Use a public http(s) URL, or upload the audio and play it as an asset.

A <Play asset="…"> named an asset that does not exist in this account, is inactive, or has no playable audio. The call continues with the next verb. This event exists because the failure is otherwise invisible: a failed playback does not end the channel, so the caller simply hears silence.

Audio capture began for the call.

A recording finished and its Recording row is available.

A <Say> utterance was synthesised; carries seconds + tier used (billed separately).

A status-callback / voice webhook was delivered successfully.

A webhook delivery failed after retries.

An end-user phone token was validated and accepted.

An end-user phone token was expired or revoked.

A usage charge was posted to the wallet after a call/segment settled.

The live monitor warns the account is nearing its allowance on active calls.

A new call was refused before answer because the account has no spendable allowance.

The live billing monitor terminated the call because the allowance ran out mid-call.

A new call was refused because the account is not active/suspended.

A new call was refused: the account already has its maximum simultaneous calls.

A new call was refused: the account is placing calls faster than its CPS limit.

An end-user tried to place an outbound PSTN call but the account owns no active DID to present as caller ID. Acquire/activate a number first.

An end-user dialled an extension belonging to a different account. On-net calls are only allowed between extensions of the same account.

An end-user dialled an on-net extension whose softphone is not currently registered (offline). The callee must be online to receive the call.

An end-user placed an outbound PSTN call but the account has no outbound voice webhook configured. SautiPBX never dials the PSTN on its own: call control comes from your <Response> XML, so the caller heard a short notice and the call was ended. Configure an outbound voice webhook to place the call.

The current platform-wide active-call count (staff stream).

The call was refused because the platform is briefly not admitting new calls. An operator closed the call gate for a deploy or maintenance window. It is not a problem with your account, your balance or your request: calls already in progress continue, and the gate reopens automatically within minutes. Retry shortly.

An internal error occurred handling a call event; carries a traceId to correlate.

A party hung up normally after a connected call.

The dialled number is not a live DID on the platform (unknown or inactive account).

The call hit the account’s absolute maximum-duration ceiling and was cut.

The call rang out with no answer.

The far end was busy.

The caller hung up before the call was answered.

SautiPBX ended the leg as a protective measure, to bound spend while a billing check could not be completed. This is a deliberate platform cut, not a carrier failure, and not a fault in your application. Retrying is safe.

The call ended, but the hangup notification was lost in transit inside the platform, so the CDR was closed by a reconcile sweep against the list of calls still live on the switch. endedAt is when we detected the loss, not when the carrier released the call; the sweep runs within a minute of the gap, so it is close, but it is a detection time. Talk time is billed only when the dialled leg already reported it, never guessed.

The carrier or network failed the call (temporary failure, out-of-order, bearer mismatch).

The leg was rejected before answer (e.g. a malformed/unattributable outbound leg).