> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Call recording pipeline: from call end to a playable recording

> How a SIPREC capture becomes a playable artifact on the call record — the recorder-to-upload-to-callback lifecycle, the pending/failed states you may observe, how uploads are authenticated, and where the playback URL and exports pick the recording up.

# Call recording pipeline

Every call you record produces two artifacts on two different actors: the capture, made by the recording plane your [media-planes](/concepts/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](/api-reference/endpoints/voice) 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:

1. **Recorder stops and stages the file** — at call end the recording plane finalizes the audio into a staged file (`.wav` on the carrier plane). The call record has already advanced to its terminal status; this step starts alongside it.
2. **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.
3. **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.
4. **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.completed` webhook at this point; the recording is now visible in the [recording library](/voice/recording-library) and on any export that includes call artifacts.

Two properties follow from this design:

* **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.completed` and `call.recording.ready` can arrive in either order, and the recording webhook may arrive minutes after the call's terminal event. Key both to `call_id`, never to arrival order (the [webhook events reference](/reference/webhook-events) pins the per-event payloads; the [delivery-semantics](/concepts/webhook-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:

| State                   | What it means                                                                                                                                                                       | What you see                                                                                                                                                                                             |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Recording pending**   | The call ended but the staged file is still in flight — uploading or retrying. This is the "where is my recording" answer for the minutes after a call.                             | No `call.recording.ready` webhook yet; the call record is complete; the recording does not yet appear in the [recording library](/voice/recording-library) or exports.                                   |
| **Recording available** | The upload landed and the callback stamped the call record.                                                                                                                         | `call.recording.ready` / `recording.completed` fired with a `recording_url`; the recording answers in the library, playback, and exports.                                                                |
| **Recording failed**    | The uploader gave up after retries and emitted a permanent-failure marker, or the capture itself failed upstream (codec mismatch, storage write failure, AMD aborted before audio). | `recording.failed` fired with a machine-readable `reason` (`storage_write_failed`, and similar). Operator-side alerting picks the permanent-failure marker up so the platform sees it even if you don't. |

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](/api-reference/recordings)) 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.

One corollary worth holding: the upload callback deliberately carries **no tenant identifier** — the platform resolves the owning account by walking the call identifier after the signature verifies, so a forged envelope cannot even nominate which account to write to. Tenant-owned security controls (retention, legal hold, sealing) apply only after the artifact lands, as documented in the [Recordings API](/api-reference/recordings).

## Where to consume recordings

Once the pipeline completes, the recording is available through four surfaces, each documented on its own page:

* **Playback** — `recording_url` on the `call.recording.ready` / `recording.completed` payload and on the recording object the [Recordings API](/api-reference/recordings) 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](/voice/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](/guides/conversation-archive) export (CSV/JSON) picks recordings up alongside the conversations they belong to once they are available; the `recording.retention_deleted` webhook 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](/reference/webhook-events) 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](/voice/recording-library) page rather than here.

## Common pitfalls

1. **Alerting on `call.completed`-without-`call.recording.ready` as a failure.** The pipeline takes minutes on a healthy day and longer under retry. Alert on `recording.failed`, or on a `call.completed`-then-no-`recording.*` window longer than your own SLA — not on the gap itself.
2. **Assuming a failure status on the call means a failed recording.** The capture is decoupled from the call outcome: a `no-answer` call can still upload a voicemail-screening clip, and a `completed` call can still fail to upload. Branch on the recording events for recording state, not the call's terminal status.
3. **Ordering reconciliation by webhook arrival.** `call.completed` and `call.recording.ready` arrive in either order; the recording event can lag the terminal call event by minutes. Reconcile by `call_id`, never by sequence.
4. **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.ready` before exporting or consume retention and ready events as a reconciliation feed for the archive you maintain.
5. **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](/api-reference/recordings).

With the pipeline in mind, the per-event payloads are in the [webhook events reference](/reference/webhook-events), the per-call capture verbs are in the [Voice API reference](/api-reference/endpoints/voice), and the post-landing recording surface is the [Recordings API](/api-reference/recordings).
