> 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. # Room > Complete Room resource and event reference for room lifecycle, participants, and room playback. A `Room` is a shared audio resource identified by `room_id`. The full resource returned by the get method reflects room playback and a room [Recording](/api/recording). ## When to use Use a Room to connect two or more Sessions in one conversation, such as a caller and an agent or a group call. ## Room lifecycle Use the events on this page to react to creation, confirmed deletion, participant changes, and room playback outcomes. Use the get method to reconcile the latest Room state after a missed event. ## Methods * [Create a room](https://docs.vorbal.dev/api/rooms/create) * [List rooms](https://docs.vorbal.dev/api/rooms/list) * [Get room details](https://docs.vorbal.dev/api/rooms/get) * [Delete a room](https://docs.vorbal.dev/api/rooms/delete) The list method returns a `RoomSummary` projection for each room. Use the get method when you need the complete `Room` representation shown below. ## Enable participant voice-activity events Room voice-activity events are opt-in. To receive [`room.participant.voice_activity_changed`](/api/rooms#roomparticipantvoice_activity_changed) events when participants start and stop talking, create the Room with `voice_activity_events` set to `true`: ```json { "voice_activity_events": true } ``` If you omit `voice_activity_events` or set it to `false`, the Room does not emit voice-activity events. The `voice_activity_events` property on the Room resource reports the value selected when the Room was created. ## Resource representation ### The Room resource ```json { "created_at": "2023-01-01T00:00:00Z", "participant_count": 0, "playback_operation_uuid": "string", "playback_status": "stopped", "playback_urls": [ "string" ], "playback_volume": 0, "room_id": "string", "voice_activity_events": true } ``` ## Properties ### Schema (`Room`) ```yaml components: schemas: RoomPlaybackStatus: type: string enum: - stopped - playing - paused title: RoomPlaybackStatus Room: type: object properties: created_at: type: string format: date-time participant_count: type: integer description: Current number of participants in the room. playback_operation_uuid: type: string playback_status: $ref: '#/components/schemas/RoomPlaybackStatus' playback_urls: type: array items: type: string format: uri playback_volume: type: integer minimum: 0 maximum: 200 default: 0 description: |- Current room playback volume (0-200). A newly created room reports `0` until a room playback volume command sets an explicit value. room_id: type: string voice_activity_events: type: boolean description: >- Whether participant voice activity webhooks are enabled for this room. required: - created_at - participant_count - playback_operation_uuid - playback_status - playback_urls - playback_volume - room_id - voice_activity_events title: Room ``` ## Lifecycle, participant, and playback events Use room events to track room lifecycle, participants, and playback. > **Note** > > **Event ordering:** All `room.*` events for this Room, including events for > different participants, are delivered in production order. Failures hold this Room, > not other Rooms. See [Ordering guarantees](/api/event-delivery#ordering-guarantees). > **Info** > > **Event envelope:** Every room event also includes the required `aud` and > `timestamp` fields. `aud` identifies the application that should receive the > signed event. `timestamp` is the emission time as an RFC 3339 / ISO 8601 UTC > string, such as `"2026-06-28T14:30:00.000Z"`. Treat `timestamp` as event-time > metadata, not as a total-order key. > > Delivery requests carry W3C trace context in their headers. > **Note** > > **Participant identity:** Room participant events identify a participant by `session_uuid`. > For an inbound Session, the caller number is available on its `session.created` > webhook. For an outbound Session, use the caller number from your dial request. ## room.created The platform emits this event when `POST /api/v1/rooms` creates a room. | Field | Type | Description | | ---------- | ------ | ------------------------------------- | | `event` | string | Always `"room.created"`. **Required** | | `app_uuid` | string | Application identifier. **Required** | | `room_id` | string | Unique room identifier. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.created", "app_uuid": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "room_id": "00000000-0000-4000-8000-000000000301", "timestamp": "2026-06-28T14:30:00.000Z" } ``` ## room.deleted The platform emits this event after processing a room deletion request. `DELETE /api/v1/rooms/{room_id}` first returns `202 Accepted` with `{"room_id": "...", "status": "deleting"}`. This response only acknowledges that the request was admitted. The event arrives when the platform confirms that the room is gone (`reason: "closed"`) or when that confirmation times out (`reason: "timeout"`). The `reason` field is always present. | Field | Type | Description | | ---------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"room.deleted"`. **Required** | | `app_uuid` | string | Application identifier. **Required** | | `room_id` | string | Unique room identifier. **Required** | | `reason` | string | Why the room was torn down. One of `"closed"` (normal confirmed teardown) or `"timeout"` (the room was reaped after teardown confirmation never arrived). Always present. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.deleted", "app_uuid": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "room_id": "00000000-0000-4000-8000-000000000301", "reason": "closed", "timestamp": "2026-06-28T14:31:00.000Z" } ``` ## room.participant.joined The platform emits this event after a new participant joins a Room. [`POST /api/v1/rooms/{room_id}/participants`](/api/room-participants/add) returns `202 Accepted` with status `"joining"` when it starts a join. Wait for `room.participant.joined` to confirm that the participant is in the Room. If the Session is already in the Room, the endpoint returns `200 OK` with status `"already_joined"` and does not emit a new `room.participant.joined` event. | Field | Type | Description | | -------------- | ------ | ----------------------------------------------------- | | `event` | string | Always `"room.participant.joined"`. **Required** | | `session_uuid` | string | Session UUID of the joining participant. **Required** | | `room_id` | string | Room identifier. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.participant.joined", "session_uuid": "00000000-0000-4000-8000-000000000101", "room_id": "00000000-0000-4000-8000-000000000301", "timestamp": "2026-06-28T14:32:00.000Z" } ``` ## room.participant.left The platform emits this event when a Session leaves a Room through [`DELETE /api/v1/rooms/{room_id}/participants/{uuid}`](/api/room-participants/remove), moves to another Room, hangs up, or is removed during stale-session cleanup. The `reason` value identifies how the participant left: * `"hangup"` means the participant's channel hung up. * `"removed"` means an explicit removal through the Room leave endpoint or a move to another Room. * `"orphan"` means the platform detected that the participant's call had already gone away, typically because of a crash or dropped connection, and removed the stale participation. Treat this as an abnormal disconnect, not a clean leave. | Field | Type | Description | | -------------- | ------ | ------------------------------------------------------------------------------ | | `event` | string | Always `"room.participant.left"`. **Required** | | `session_uuid` | string | Session UUID of the departing participant. **Required** | | `room_id` | string | Room identifier. **Required** | | `reason` | string | Departure trigger. One of `"hangup"`, `"removed"`, or `"orphan"`. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.participant.left", "session_uuid": "00000000-0000-4000-8000-000000000101", "room_id": "00000000-0000-4000-8000-000000000301", "reason": "hangup", "timestamp": "2026-06-28T14:33:00.000Z" } ``` ## room.participant.muted The platform emits this event when the `room/mute` command mutes a participant. | Field | Type | Description | | -------------- | ------ | --------------------------------------------------- | | `event` | string | Always `"room.participant.muted"`. **Required** | | `session_uuid` | string | Session UUID of the muted participant. **Required** | | `room_id` | string | Room identifier. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.participant.muted", "session_uuid": "00000000-0000-4000-8000-000000000101", "room_id": "00000000-0000-4000-8000-000000000301", "timestamp": "2026-06-28T14:34:00.000Z" } ``` ## room.participant.unmuted The platform emits this event when the `room/unmute` command unmutes a participant. | Field | Type | Description | | -------------- | ------ | ----------------------------------------------------- | | `event` | string | Always `"room.participant.unmuted"`. **Required** | | `session_uuid` | string | Session UUID of the unmuted participant. **Required** | | `room_id` | string | Room identifier. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.participant.unmuted", "session_uuid": "00000000-0000-4000-8000-000000000101", "room_id": "00000000-0000-4000-8000-000000000301", "timestamp": "2026-06-28T14:35:00.000Z" } ``` ## room.participant.volume_changed The platform emits this terminal success event after every direction requested by [`POST /v1/sessions/{uuid}/room/volume`](/api/room-participants/volume) succeeds. For `direction: "both"`, the event arrives only after both volume assignments succeed. | Field | Type | Description | | ---------------- | ------- | ---------------------------------------------------------------------------------------- | | `event` | string | Always `"room.participant.volume_changed"`. **Required** | | `session_uuid` | string | Session UUID of the participant. **Required** | | `room_id` | string | Room identifier. **Required** | | `operation_uuid` | string | Correlation identifier from the accepted volume command. **Required** | | `volume` | integer | Applied volume level from `1` through `9`. **Required** | | `direction` | string | Requested direction: `"to_participant"`, `"from_participant"`, or `"both"`. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.participant.volume_changed", "session_uuid": "00000000-0000-4000-8000-000000000101", "room_id": "00000000-0000-4000-8000-000000000301", "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123", "volume": 7, "direction": "both", "timestamp": "2026-06-28T14:35:30.000Z" } ``` ## room.participant.volume_change_failed The platform emits this terminal failure event after a confirmed full or partial failure of an accepted participant-volume command. The direction lists show which assignments succeeded before the final result and which assignments failed. | Field | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------ | | `event` | string | Always `"room.participant.volume_change_failed"`. **Required** | | `session_uuid` | string | Session UUID of the participant. **Required** | | `room_id` | string | Room identifier. **Required** | | `operation_uuid` | string | Correlation identifier from the accepted volume command. **Required** | | `volume` | integer | Requested volume level from `1` through `9`. **Required** | | `direction` | string | Requested direction: `"to_participant"`, `"from_participant"`, or `"both"`. **Required** | | `reason` | string | Stable failure category. Always `"execution_failed"`. **Required** | | `applied_directions` | string\[] | Directions applied before the final result. Each value is `"to_participant"` or `"from_participant"`. **Required** | | `failed_directions` | string\[] | Directions whose execution failed. Each value is `"to_participant"` or `"from_participant"`. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.participant.volume_change_failed", "session_uuid": "00000000-0000-4000-8000-000000000101", "room_id": "00000000-0000-4000-8000-000000000301", "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123", "volume": 7, "direction": "both", "reason": "execution_failed", "applied_directions": ["from_participant"], "failed_directions": ["to_participant"], "timestamp": "2026-06-28T14:35:45.000Z" } ``` ## room.participant.voice_activity_changed Emitted when a participant starts (`talking: true`) or stops (`talking: false`) speaking. These events can be frequent, so debounce them in your application. > **Note** > > VAD state is delivered via this webhook. The participant object returned by [`GET /api/v1/rooms/{room_id}/participants/{uuid}`](/api/room-participants/get) contains only `{uuid, muted}`. | Field | Type | Description | | -------------- | ------- | ------------------------------------------------------------------------------------ | | `event` | string | Always `"room.participant.voice_activity_changed"`. **Required** | | `session_uuid` | string | Session UUID of the participant whose VAD state changed. **Required** | | `room_id` | string | Room identifier. **Required** | | `talking` | boolean | `true` when the participant started talking, `false` when they stopped. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.participant.voice_activity_changed", "session_uuid": "00000000-0000-4000-8000-000000000101", "room_id": "00000000-0000-4000-8000-000000000301", "talking": true, "timestamp": "2026-06-28T14:36:00.000Z" } ``` ## room.playback.file.ended The platform emits this event when a finite URL file that started playing in a room completes or playback is stopped through the API. A single-file request also produces this event. For a playlist, use `file_index` to identify the entry in the original `urls` array; `url` repeats the original URL supplied for that entry. A file that fails or never starts does not produce this event. The file event arrives before the operation's terminal `room.playback.ended` or `room.playback.failed` event. With `loop: true`, each completed repeat produces another file event with the same `file_index` and `url`. | Field | Type | Description | | ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | | `event` | string | Always `"room.playback.file.ended"`. **Required** | | `room_id` | string | Room identifier. **Required** | | `operation_uuid` | string | Correlation identifier from the original [play request](/api/room-playback/play)'s `202 Accepted` response. **Required** | | `file_index` | integer | Zero-based index in the original `urls` array. **Required** | | `url` | string | Original URL supplied for this entry. **Required** | | `reason` | string | `"completed"` when the file finishes, or `"stopped"` when playback is stopped through the API. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.playback.file.ended", "room_id": "00000000-0000-4000-8000-000000000301", "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123", "file_index": 0, "url": "https://cdn.example.com/audio/announcement.wav", "reason": "completed", "timestamp": "2026-10-18T14:37:00.000Z" } ``` ## room.playback.ended This terminal event is emitted once after all files in the play request finish or playback is stopped through the API. It describes the whole play operation. Individual finite files that started have their own [`room.playback.file.ended`](#roomplaybackfileended) events. Failures and start-confirmation timeouts produce `room.playback.failed` instead. | Field | Type | Description | | ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `event` | string | Always `"room.playback.ended"`. **Required** | | `room_id` | string | Room identifier. **Required** | | `operation_uuid` | string | Correlation identifier echoed from the originating [play request](/api/room-playback/play)'s `202 Accepted` response. An API stop does not replace it with the stop command's correlation UUID. **Required** | | `urls` | string\[] | The original file `urls` playlist in request order. For a live stream, this is `[]`; use `operation_uuid` to correlate the event with its play request. **Required** | | `reason` | string | Why playback ended: `"completed"` when every source finished naturally, or `"stopped"` when interrupted through the API. **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.playback.ended", "room_id": "00000000-0000-4000-8000-000000000301", "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123", "urls": ["https://cdn.example.com/audio/announcement.wav"], "reason": "completed", "timestamp": "2026-06-28T14:37:00.000Z" } ``` ## room.playback.failed The platform emits this event when room playback fails. A failure occurs when the platform reports a playback error, any audio file in the request's `urls` array fails to play, or the platform does not receive start confirmation within 10 seconds. One failed audio file fails the entire operation. Playback that starts within the 10-second window can continue for longer without failing. This event resets `playback_status` to `"stopped"` so the room can accept a new play. | Field | Type | Description | | ---------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event` | string | Always `"room.playback.failed"`. **Required** | | `room_id` | string | Room identifier. **Required** | | `operation_uuid` | string | Non-empty lowercase UUID v4 echoed from the originating [play request](/api/room-playback/play)'s `202 Accepted` response. **Required** | | `urls` | string\[] | The original file `urls` playlist in request order. For a live stream, this is `[]`; use `operation_uuid` to correlate the event with its play request. **Required** | | `error` | string | Coarse-grained error bucket. One of `"playback_error"` (the platform reported a playback failure) or `"playback_timeout"` (playback start was not confirmed within 10 seconds). **Required** | ```json { "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34", "event": "room.playback.failed", "room_id": "00000000-0000-4000-8000-000000000301", "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123", "urls": ["https://cdn.example.com/audio/announcement.wav"], "error": "playback_timeout", "timestamp": "2026-06-28T14:38:00.000Z" } ``` ## Triggered by These events are produced by the following endpoints: * [`room.created`](#roomcreated) — [POST /rooms](/api/rooms/create). * [`room.deleted`](#roomdeleted) — automatic: fires on confirmed teardown after [DELETE /rooms/\{room_id}](/api/rooms/delete) returns `202 Accepted` (`reason: "closed"`), or with `reason: "timeout"` if confirmation never arrives. * [`room.participant.joined`](#roomparticipantjoined) — [POST /rooms/\{room_id}/participants](/api/room-participants/add) (asynchronous). * [`room.participant.left`](#roomparticipantleft) — [DELETE /rooms/\{room_id}/participants/\{uuid}](/api/room-participants/remove), a move to another Room, automatic hangup, or stale-session cleanup. * [`room.participant.muted`](#roomparticipantmuted) — [room/mute](/api/room-participants/mute). * [`room.participant.unmuted`](#roomparticipantunmuted) — [room/unmute](/api/room-participants/unmute). * [`room.participant.volume_changed`](#roomparticipantvolume_changed) — terminal success after all requested volume directions succeed. * [`room.participant.volume_change_failed`](#roomparticipantvolume_change_failed) — terminal failure after a full or partial volume execution failure. * [`room.participant.voice_activity_changed`](#roomparticipantvoice_activity_changed) — automatic: VAD state change. * [`room.playback.file.ended`](#roomplaybackfileended) — a finite URL file that started playing completes or playback is stopped through the API. * [`room.playback.ended`](#roomplaybackended) — terminal: fires once when playback completes naturally or is stopped through the API. * [`room.playback.failed`](#roomplaybackfailed) — automatic: fires on a playback failure or when the playback watchdog times out. For room deletion, participant volume, and room playback play, pause, resume, stop, and volume commands, only the request's `202 Accepted` response acknowledges admission. > Build reliable calling experiences with Vorbal APIs for calls, media, messaging, and webhooks. ## API Docs - Rooms [List rooms](https://docs.vorbal.dev/api/rooms/list.md) - Rooms [Get room status](https://docs.vorbal.dev/api/rooms/get.md) - Rooms [Create a room](https://docs.vorbal.dev/api/rooms/create.md) - Rooms [Delete room](https://docs.vorbal.dev/api/rooms/delete.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)