> For complete lifecycle-event payload schemas and examples, use the canonical overview: Session /api/sessions; Room lifecycle, members, 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, or Silence tab. Pause, resume, restart, and stop work with every
playback source. Seek is available only during file playback.

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": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "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.

**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.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   | Total playback duration in milliseconds. 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": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "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 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": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "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.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.