Recording events

Track recording progress and speech activity through webhooks.
View as Markdown

Recording events notify your application as a session or room Recording moves through its lifecycle. Session Recordings can fail their liveness check and can opt into customer speech activity events.

The per-command correlation field is operation_uuid, echoed from the command’s 202 Accepted Operation.

Session and room Recordings use the same lifecycle event names and the same public recording_uuid. Session events carry session_uuid; room events carry room_id.

command.recording.start.accepted

Fired immediately after a recording start command is dispatched. Its pull_url fetches the whole recording so far and can be opened while recording is in progress. A session recording emits either recording.became_available or recording.failed, normally within about 10 seconds. A room recording emits recording.became_available only when liveness is confirmed; otherwise it remains active without a failure webhook.

Payload schema

FieldTypeDescription
eventstringAlways "command.recording.start.accepted". Required
session_uuidstringSession being recorded. Session recordings
room_idstringRoom being recorded. Room recordings
recording_uuidstringRecording identifier (rec_<uuid-v4>). Required
pull_urlstringURL for the whole recording so far. Required
expires_atstringExact RFC 3339 UTC expiration time for pull_url. Required
operation_uuidstringCorrelation ID of the recording start command. Required
timestampstringRFC 3339 / ISO 8601 UTC emission timestamp. Required
1{
2 "event": "command.recording.start.accepted",
3 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
4 "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
5 "pull_url": "https://recordings.example.com/c/opaque-signed-url-1",
6 "expires_at": "2025-06-16T14:50:00.000Z",
7 "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
8 "timestamp": "2025-06-15T14:50:00.000Z"
9}

recording.speech.started

Fired when customer speech is detected during a session recording started with enable_voice_activity_events: true. The first event omits preceding_silence_duration_ms; later events include the measured silence since the preceding recording.speech.ended. sensitivity controls how quiet a sound can be and still count as speech; speech_duration_ms controls how long speech must continue before this event. Both trade faster or quieter detection against more false starts. See the recording/start request schema for ranges and recommended values.

FieldTypeDescription
eventstringAlways "recording.speech.started". Required
session_uuidstringSession being recorded. Required
recording_uuidstringRecording identifier (rec_<uuid-v4>). Required
operation_uuidstringCorrelation ID of the originating recording start command. Required
preceding_silence_duration_msintegerSilence before speech resumed. Omitted for the first speech interval.
timestampstringRFC 3339 / ISO 8601 UTC transition timestamp. Required

recording.speech.ended

Fired after customer audio remains below the configured threshold for silence_duration_ms. Lower values report an end faster but can split natural pauses; higher values tolerate pauses but delay this event. It is never emitted before the first recording.speech.started, and neither speech event is emitted after the recording stops or the session hangs up.

FieldTypeDescription
eventstringAlways "recording.speech.ended". Required
session_uuidstringSession being recorded. Required
recording_uuidstringRecording identifier (rec_<uuid-v4>). Required
operation_uuidstringCorrelation ID of the originating recording start command. Required
timestampstringRFC 3339 / ISO 8601 UTC transition timestamp. Required

recording.became_available

Fired after command.recording.start.accepted once the gateway confirms that audio is reaching durable storage and is fetchable. Wait for this event when you need that guarantee before acting. It carries two URLs:

  • pull_url — the whole recording so far, WAV-wrapped. It streams a live chunked tail while recording is active, then serves the completed file with HTTP Range support. Download this URL to keep a copy.
  • live_url — a non-seekable live tail stream from the current position. A listener joins at the live edge, hears “now” forward, and does not replay the beginning. Use it to monitor an active recording.

For a session recording, liveness not confirmed within the validation window emits recording.failed instead. Room recordings emit recording.became_available only on success; they do not emit a liveness-failure webhook. recording.became_available and recording.failed are mutually exclusive outcomes for a session recording.

Payload schema

FieldTypeDescription
eventstringAlways "recording.became_available". Required
session_uuidstringSession being recorded. Session recordings
room_idstringRoom being recorded. Room recordings
recording_uuidstringRecording identifier (rec_<uuid-v4>). Required
pull_urlstringURL for the whole-so-far WAV. Required
live_urlstringURL for the non-seekable live tail stream. Required
expires_atstringExact RFC 3339 UTC expiration time shared by both URLs. Required
operation_uuidstringCorrelation ID of the recording start command. Required
timestampstringRFC 3339 / ISO 8601 UTC emission timestamp. Required
1{
2 "event": "recording.became_available",
3 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
4 "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
5 "pull_url": "https://recordings.example.com/c/opaque-signed-url-1",
6 "live_url": "https://recordings.example.com/c/opaque-signed-url-2",
7 "expires_at": "2025-06-16T14:50:01.000Z",
8 "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
9 "timestamp": "2025-06-15T14:50:01.000Z"
10}

recording.ended

Fired when an active recording is stopped. Session recordings also emit this event when the session hangs up while recording.

For both session and room recordings, operation_uuid identifies the originating recording/start command when available. A later stop response has its own UUID, which is not copied into this lifetime event.

Payload schema

FieldTypeDescription
eventstringAlways "recording.ended". Required
session_uuidstringSession that was recorded. Session recordings
room_idstringRoom that was recorded. Room recordings
recording_uuidstringRecording identifier (rec_<uuid-v4>). Required
pull_urlstringFresh URL for the completed Recording. Required
expires_atstringExact RFC 3339 UTC expiration time for pull_url. Required
operation_uuidstringCorrelation ID of the originating recording/start command for session and room recordings. Present when known
durationintegerWall-clock recording duration in whole seconds. Required
timestampstringRFC 3339 / ISO 8601 UTC emission timestamp. Required
1{
2 "event": "recording.ended",
3 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
4 "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
5 "pull_url": "https://recordings.example.com/c/opaque-signed-url-3",
6 "expires_at": "2025-06-16T14:51:02.000Z",
7 "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
8 "duration": 62,
9 "timestamp": "2025-06-15T14:51:02.000Z"
10}

recording.failed

recording.failed is session-only. It fires when the post-dispatch liveness check does not confirm that a new session Recording is reaching durable storage within the validation window. This event is emitted instead of recording.became_available, with the error "recording unavailable" and no usable URL. A room liveness timeout does not emit this event or end the room Recording.

This failure completes the start-liveness check. A later stop or hangup can still emit recording.ended. If recording is not configured, the start request returns 503 and no recording webhook is emitted.

Payload schema

FieldTypeDescription
eventstringAlways "recording.failed". Required
session_uuidstringSession whose recording failed the liveness check. Required
recording_uuidstringRecording identifier (rec_<uuid-v4>). Required
operation_uuidstringCorrelation ID of the recording start command. Required
errorstringCurrently "recording unavailable". Required
pull_urlstringEmpty because no URL was minted; there is no validity window. Required
timestampstringRFC 3339 / ISO 8601 UTC emission timestamp. Required
1{
2 "event": "recording.failed",
3 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
4 "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
5 "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
6 "error": "recording unavailable",
7 "pull_url": "",
8 "timestamp": "2025-06-15T14:50:10.000Z"
9}

command.recording.mask.accepted

Fired when recording/mask is accepted for an active session recording. The command returns 409 Conflict when no recording is active, and 500 Internal Server Error if it cannot be accepted. This event confirms the masking request was accepted.

The recording stays active while audio writing is masked. A matching command.recording.unmask.accepted event is emitted when writing is requested to resume.

Payload schema

FieldTypeDescription
eventstringAlways "command.recording.mask.accepted". Required
session_uuidstringSession identifier. Required
recording_uuidstringRecording identifier (rec_<uuid-v4>). Required
operation_uuidstringCorrelation ID echoed from the 202 Accepted response for recording/mask. Required
pull_urlstringFresh URL for the Recording.
expires_atstringExact RFC 3339 UTC expiration time. Present with pull_url.
timestampstringRFC 3339 / ISO 8601 UTC emission timestamp. Required
1{
2 "event": "command.recording.mask.accepted",
3 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
4 "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
5 "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
6 "pull_url": "https://recordings.example.com/c/opaque-signed-url-4",
7 "expires_at": "2025-06-16T14:50:30.000Z",
8 "timestamp": "2025-06-15T14:50:30.000Z"
9}

command.recording.unmask.accepted

Fired when recording/unmask is accepted for an active session recording. This event confirms the request to resume audio writes was accepted. Calling recording/unmask when the active recording is already unmasked is harmless.

Payload schema

FieldTypeDescription
eventstringAlways "command.recording.unmask.accepted". Required
session_uuidstringSession identifier. Required
recording_uuidstringRecording identifier (rec_<uuid-v4>). Required
operation_uuidstringCorrelation ID echoed from the 202 Accepted response for recording/unmask. Required
pull_urlstringFresh URL for the Recording.
expires_atstringExact RFC 3339 UTC expiration time. Present with pull_url.
timestampstringRFC 3339 / ISO 8601 UTC emission timestamp. Required
1{
2 "event": "command.recording.unmask.accepted",
3 "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
4 "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
5 "operation_uuid": "b2c3d4e5-f6a7-4890-abcd-ef1234567890",
6 "pull_url": "https://recordings.example.com/c/opaque-signed-url-5",
7 "expires_at": "2025-06-16T14:50:45.000Z",
8 "timestamp": "2025-06-15T14:50:45.000Z"
9}

Recording lifecycle

Session recordings

  1. A start command emits command.recording.start.accepted immediately.
  2. The liveness check emits exactly one of recording.became_available or recording.failed.
  3. A stop command or session hangup emits recording.ended.

In short: command.recording.start.accepted → (recording.became_available | recording.failed) → recording.ended.

recording.became_available and recording.failed are mutually exclusive. recording.failed and a later recording.ended are not mutually exclusive.

Room recordings

  1. A start command emits command.recording.start.accepted immediately.
  2. A successful liveness check emits recording.became_available; no event is emitted when the check does not confirm liveness.
  3. A room recording stop emits recording.ended.

Room recordings emit recording.became_available when liveness succeeds and do not emit recording.failed.

Recording URLs

Fetch recording URLs without an Authorization header. Audio responses use Content-Type: audio/wav.

  • expires_at is the exact expiry. The default validity window is 24 hours and may be configured differently.
  • Fresh URLs are minted independently for lifecycle events. In particular, recording.ended provides a new default 24-hour download window after stop.
  • Fetching a URL does not extend its validity.
  • Invalid, expired, malformed, or altered URLs return 403 Forbidden.
  • Unknown recordings return 404 Not Found; recordings removed after retention return 410 Gone.
  • pull streams the whole recording so far while active and supports HTTP Range after completion.
  • live tails from the current offset and is not Range-capable.

Applications that need longer-lived audio must download the WAV before the chosen URL expires and store it themselves.

Triggered by

  • command.recording.start.acceptedPOST /v1/sessions/{uuid}/recording/start and POST /api/v1/rooms/{room_id}/playback/record.
  • recording.became_available — the successful liveness-check outcome after either start command.
  • recording.failed — the unsuccessful liveness-check outcome after a session recording start only.
  • recording.endedPOST /v1/sessions/{uuid}/recording/stop, session hangup while recording, and POST /api/v1/rooms/{room_id}/playback/record/stop.
  • command.recording.mask.acceptedPOST /v1/sessions/{uuid}/recording/mask.
  • command.recording.unmask.acceptedPOST /v1/sessions/{uuid}/recording/unmask.