Call recording pipeline
Every call you record produces two artifacts on two different actors: the capture, made by the recording plane your media-planes page assigns (SIPREC on the carrier plane for SIP calls, server-side egress on Orbit’s media plane for video rooms), and the playable recording your dashboard and API expose — an uploaded audio file on the call record. This page covers the hop between the two: how a finished capture becomes the playable artifact, what you observe while that hop is in flight, and where the playback surface lives. Read it when a customer asks “where is my recording?” or when you wire exports against recorded calls. The capture-side verbs a call passes (record:true on placement, POST /api/v1/voice/calls/{id}/recording/start, mid-call pause/resume) live in the Voice API reference and produce the in-call call.recording.paused / call.recording.resumed events. This page begins where those end — when the call hangs up and the recorder finalizes its file.
What SIPREC is, at the account level
For SIP-trunked calls, the recording is captured by the network side, not by your application. The carrier-facing plane records the call as it flows through (in-call SIPREC is the verb the media-planes page names), streams the audio into a staged file while the call runs, and closes that file when the call ends. Orbit then ingests the staged file: an uploading component picks it up, ships it to object storage, and calls the Orbit API back so the recording lands on the call record you already have — as a recording identifier with a playable URL. The customer-observable consequence is a gap of a few seconds to minutes between call end and playable recording. During that gap the call record exists and its lifecycle is complete (completed, failed, no-answer are already terminal) — only the recording is in transit. This is a pending upload, not a missing call.
The upload lifecycle
From the recorder stopping to the recording becoming playable, the path is:- Recorder stops and stages the file — at call end the recording plane finalizes the audio into a staged file (
.wavon the carrier plane). The call record has already advanced to its terminal status; this step starts alongside it. - Upload to object storage — the uploader ships the staged file to Orbit’s storage backend. Retries happen here on transient failure, with exponential backoff between attempts.
- Authenticated callback to Orbit — when the upload succeeded (or has permanently failed), the uploader posts a signed envelope to an internal Orbit endpoint. The signature binds the exact request bytes so the callback cannot be forged or replayed across endpoints — see Security below.
- The call record is stamped — the API resolves which call the envelope belongs to, writes the recording to the library, and makes the playable URL available. Your account sees the call’s
call.recording.ready/recording.completedwebhook at this point; the recording is now visible in the recording library and on any export that includes call artifacts.
- The pipeline is decoupled from the call’s final state. A call that never answered, hit voicemail, or failed on the network still uploads whatever was captured — so a failure status on the call does not imply a failed recording. Conversely a completed call can still carry a recording that failed to upload.
- Webhook order is not lifecycle order.
call.completedandcall.recording.readycan arrive in either order, and the recording webhook may arrive minutes after the call’s terminal event. Key both tocall_id, never to arrival order (the webhook events reference pins the per-event payloads; the delivery-semantics concept pins the delivery model).
Failure, retry, and the pending/failed distinction
Transient upload failure retries automatically. The uploader re-attempts shipments that fail for operational reasons (an unreachable storage endpoint, a timeout) with backoff, and the retry loop raises the observation level when a file keeps failing. Three customer-visible states follow:
The distinction to hold clearly: a pending recording is not an error and needs no action — waiting is correct. A failed recording is terminal: re-placement of the call is the only recovery path, since the capture plane records at call time and a new upload of the same file is not offered. Retry between those two states is entirely platform-owned; there is no customer-facing “re-upload” verb, and a retention sweep will not remove a recording before the upload resolves (a failed-marker recording is the only state in which the call record and the artifact diverge durably).
Security, at the account level
The transfer path from recorder to your account is authenticated and signed on both hops:- Recorder → storage — the upload runs over the storage backend’s authenticated control plane; the staged file never becomes a public object.
- Storage → Orbit — the completion callback is HMAC-signed over the method, the exact URL, and the exact request body, with a timestamp bound in. The receiving API re-derives the signature against the raw bytes, rejects envelopes that don’t verify or that are replayed, and the signing key is platform-internal — your tenant never handles it.
- Storage → you — playback and export travel over authenticated paths on your side. A recording URL is served through the recordings API scope set (playback requires
voice:read; the compliance actions on a recording — legal hold, integrity seal, share links — are in the Recordings API reference) or through the dashboard over your session. The AI-agent/evaluation surfaces that consume recordings further require reviewer-class roles, so raw audio does not leak to an agent-scoped token.
Where to consume recordings
Once the pipeline completes, the recording is available through four surfaces, each documented on its own page:- Playback —
recording_urlon thecall.recording.ready/recording.completedpayload and on the recording object the Recordings API addresses (/api/v1/recordings/{id}/...). Stream directly from that URL, or mint a time-limited guest share link for a video-room recording you want a non-account viewer to watch. - The dashboard recording library — the recording library guide walks the searchable reviewer surface that lists every recording with transcript and QA verdicts joined. This is where a human hunts calls.
- Exports — the conversation archive export (CSV/JSON) picks recordings up alongside the conversations they belong to once they are available; the
recording.retention_deletedwebhook fires when a retention sweep is about to purge a recording so a tenant-side archive can mirror it first — see the webhook events reference for the payload. - The QA chain — evaluation scores, QC verdicts, and per-turn transcripts join each recording as they finalize. Those are post-pipeline steps and are covered on the recording library page rather than here.
Common pitfalls
- Alerting on
call.completed-without-call.recording.readyas a failure. The pipeline takes minutes on a healthy day and longer under retry. Alert onrecording.failed, or on acall.completed-then-no-recording.*window longer than your own SLA — not on the gap itself. - Assuming a failure status on the call means a failed recording. The capture is decoupled from the call outcome: a
no-answercall can still upload a voicemail-screening clip, and acompletedcall can still fail to upload. Branch on the recording events for recording state, not the call’s terminal status. - Ordering reconciliation by webhook arrival.
call.completedandcall.recording.readyarrive in either order; the recording event can lag the terminal call event by minutes. Reconcile bycall_id, never by sequence. - Building export jobs that race the pipeline. An export kicked off minutes after call end will capture recordings that have landed only so far. If your export must include the just-ended calls’ recordings, either wait for
call.recording.readybefore exporting or consume retention and ready events as a reconciliation feed for the archive you maintain. - Treating the upload as your tenant’s retry surface. Upload failure handling is platform-internal — there is no re-upload verb to call, and a pending recording is not actionable from your account. The operationally useful knobs you do own begin once the recording lands: playback, share links, legal hold, and retention, all on the Recordings API.