> For complete lifecycle-event payload schemas and examples, use the canonical overview: Session /api/sessions; Room lifecycle, participants, and room playback /api/rooms; Session playback /api/playback; Recording /api/recording; DTMF /api/keypad-input; Messages /api/messages; WebSocket Session /api/web-socket. Use /api/event-delivery for transport behavior. Endpoint pages name relevant events, but these overviews are the canonical references. # Playback overview > Complete Session playback command and lifecycle-event reference, including payload schemas and examples. Playback controls audio on one active session. ## When to use Use Playback when one participant should hear audio, whether it comes from a prerecorded file or a live stream. Use [Start audio playback](/api/playback/play), then choose the Audio files, Live TTS, Live stream, or Silence tab. URL-based live streams with `type: stream` do not support pause, resume, or seek. Seek is available only during file playback. > **Info** > > [Hebrew TTS Arena benchmark](https://ivrit-ai-tts-arena-hebrew.hf.space/leaderboard) > — compare Hebrew TTS models. Every control command returns `202 Accepted` when accepted for processing. Keep the originating play command's `operation_uuid` until `playback.stopped` or `playback.failed`, and reuse it if you retry after losing the response. A later stop command has its own acknowledgement UUID, while `playback.stopped` retains the play command's UUID. Session playback controls one call leg. Use [Room Playback](/api/room-playback) to control the shared room mix. ## Lifecycle events Playback events track audio playback on active calls, including start, completion, and error states. The correlation field on every webhook here is `operation_uuid`. Lifecycle events normally echo the `202 Accepted` response for the `playback/play` command that owns that playback. A terminal event caused by a later `playback/stop` retains the original start or silence operation UUID. Every payload schema below also includes the required `aud` string, which identifies the application that should receive the signed event. ## playback.started Fired when the playback application starts after it sets up the requested audio or silence source. This is a best-effort application-start signal, not confirmation that audio reached the remote caller. **Payload schema** | Field | Type | Description | | ---------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"playback.started"`. **Required** | | `session_uuid` | string | Unique session identifier. **Required** | | `operation_uuid` | string | The `operation_uuid` echoed from the 202 ack of the `playback/play` request. **Required** | | `urls` | string\[] | The original URL(s) from a `playback/play` request, as an array. File sources contain their original URLs, a pushed live stream emits `[]`, and silence emits `["silence"]`. **Required** | | `timestamp` | string | RFC 3339 / ISO 8601 UTC timestamp at which the platform emitted the `playback.started` event (e.g. `"2024-06-10T06:13:20.000Z"`). **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "playback.started", "session_uuid": "00000000-0000-4000-8000-000000000101", "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", "urls": ["https://cdn.example.com/audio/greeting.wav"], "timestamp": "2024-06-10T06:13:20.000Z" } ``` ### Measuring application-start latency The `timestamp` field lets you estimate application-start latency from the request to event emission: ```text latency_ms = Date.parse(playback_started.timestamp) − (the wall-clock time, in unix ms, at which you sent the POST /playback/play request) ``` Record the time you issued the `playback/play` request, parse `timestamp` when the [`playback.started`](#playbackstarted) webhook arrives, and subtract. > **Warning** > > **Cross-clock caveat — treat the result as approximate.** The event timestamp and your POST-request time come from different clocks and can differ slightly. Use the computed latency for trend monitoring and relative comparisons, not as an exact, sub-millisecond measurement. A small negative value (e.g. the webhook timestamp appearing slightly *before* your send time) is possible under clock skew and should be treated as \~0. ## playback.file.ended Fired for each finite URL file that started playing in a Session and then completed or was stopped. This includes a play request with one file. The event arrives before the operation's terminal `playback.stopped` or `playback.failed` event. A file that failed or never started has no per-file ended event; use `playback.failed` for the operation's failure. Silence, live streams, and pushed audio do not emit this event. **Payload schema** | Field | Type | Description | | ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"playback.file.ended"`. **Required** | | `session_uuid` | string | Unique Session identifier. **Required** | | `operation_uuid` | string | The `operation_uuid` of the original `playback/play` request, including when a later `playback/stop` ends this file. **Required** | | `file_index` | integer | Zero-based index of this file in the original request's `urls` array. **Required** | | `url` | string | Original URL supplied for this file in the play request. **Required** | | `reason` | string | `"completed"` when the file finished or `"stopped"` when playback stopped during the file. **Required** | | `timestamp` | string | RFC 3339 / ISO 8601 UTC emission timestamp. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "playback.file.ended", "session_uuid": "00000000-0000-4000-8000-000000000101", "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", "file_index": 0, "url": "https://cdn.example.com/audio/greeting.wav", "reason": "completed", "timestamp": "2026-10-18T08:53:24.678Z" } ``` ## playback.stopped Fired when playback completes normally or is stopped via the `playback/stop` command. The `offset_ms` field indicates where finite file playback ended. Before it accepts a finite `type: files` request, the platform runs a *request-time probe* that confirms each source can be fetched. A finite source rejected by that probe emits no playback lifecycle event. Otherwise, the outcomes are: * Each accepted playback that starts emits exactly one [`playback.stopped`](#playbackstopped) event on completion or interruption. * An accepted playback that cannot start emits [`playback.failed`](#playbackfailed). * A pushed live stream completes after the source closes normally and all accepted audio drains. It can also be replaced, stopped, or ended with the Session. Silence normally runs until one of those actions occurs. **Payload schema** | Field | Type | Description | | ---------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"playback.stopped"`. **Required** | | `session_uuid` | string | Unique session identifier. **Required** | | `operation_uuid` | string | The `operation_uuid` of the `playback/play` operation that created the playback, including when a later `playback/stop` ends it. **Required** | | `urls` | string\[] | The original file URLs from a `playback/play` request, `[]` for pushed live audio, or `["silence"]` for silence. Omitted when unavailable. *Conditional* | | `offset_ms` | integer | Playback position at stop time in milliseconds. Omitted when unavailable; treat a missing value as unknown, not 0. *Conditional* | | `duration_ms` | integer | Reported playback time in milliseconds, not the full length of the source media. Omitted when unavailable; treat a missing value as unknown, not 0. *Conditional* | | `timestamp` | string | RFC 3339 / ISO 8601 UTC emission timestamp. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "playback.stopped", "session_uuid": "00000000-0000-4000-8000-000000000101", "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", "urls": ["https://cdn.example.com/audio/greeting.wav"], "offset_ms": 15230, "duration_ms": 15230, "timestamp": "2025-06-06T08:53:25.678Z" } ``` ## playback.failed Fired when an accepted playback command cannot be completed. The request-time validation checks the request shape and URL policy before a finite `type: files` request is accepted. The platform prepares the sources asynchronously after acceptance. If a source cannot be prepared, or if an accepted playback cannot start or complete, the platform emits `playback.failed`. `playback/restart` also emits this event when no playback is active. DTMF collection does not emit this event. Inspect the `reason` field to determine why the operation failed. **Payload schema** | Field | Type | Description | | ---------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"playback.failed"`. **Required** | | `session_uuid` | string | Unique session identifier. **Required** | | `operation_uuid` | string | The `operation_uuid` echoed from the `202 Accepted` response for the `playback/play` or `playback/restart` request that failed. **Required** | | `reason` | string | Machine-readable failure reason. `"file_not_found"` means the requested media was not found. `"timeout"` means media preparation timed out. `"no_active_playback"` means `playback/restart` found nothing active. `"media_unavailable"` means media recovery or finite-file reading or decoding failed. `"preparation_failed"` is the sanitized fallback for any other preparation failure. **Required** | | `urls` | string\[] | For failed file playback, the original URL(s) that failed. Omitted when unavailable and for failures that have no source URLs. *Optional* | | `timestamp` | string | RFC 3339 / ISO 8601 UTC timestamp at which the event was emitted. Present on every webhook event. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "playback.failed", "session_uuid": "00000000-0000-4000-8000-000000000101", "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", "reason": "file_not_found", "urls": ["https://cdn.example.com/audio/missing.wav"], "timestamp": "2025-06-06T08:53:23.123Z" } ``` ## Triggered by These events are produced by the following commands: * [`playback.started`](#playbackstarted) — [playback/play](/api/playback/play) (Asynchronous). * [`playback.file.ended`](#playbackfileended) — [playback/play](/api/playback/play) (each finite URL file that ends after starting). * [`playback.stopped`](#playbackstopped) — [playback/play](/api/playback/play) (completion or interruption) and [playback/stop](/api/playback/stop). * [`playback.failed`](#playbackfailed) — [playback/play](/api/playback/play) and [playback/restart](/api/playback/restart). Playback pause, resume, seek, and restart admission is acknowledged only by each request's `202 Accepted` response; no webhook reports admission. > Build reliable calling experiences with Vorbal APIs for calls, media, messaging, and webhooks. ## API Docs - Playback [Start audio playback](https://docs.vorbal.dev/api/playback/play.md) - Playback [Pause playback](https://docs.vorbal.dev/api/playback/pause.md) - Playback [Resume playback](https://docs.vorbal.dev/api/playback/resume.md) - Playback [Seek within playback](https://docs.vorbal.dev/api/playback/seek.md) - Playback [Restart playback](https://docs.vorbal.dev/api/playback/restart.md) - Playback [Stop playback](https://docs.vorbal.dev/api/playback/stop.md) - Playback [Upload pushed playback audio](https://docs.vorbal.dev/api/playback/upload-playback-audio.md) ## OpenAPI Specification The raw OpenAPI 3.1 specification for this API is available at: - [OpenAPI JSON](https://docs.vorbal.dev/openapi.json) - [OpenAPI YAML](https://docs.vorbal.dev/openapi.yaml)