> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.telekesher.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.telekesher.dev/_mcp/server.

# Room events

Room events cover room lifecycle, member management, and playback control within rooms.

**Ordering note:** Room lifecycle events ([`room.created`](#roomcreated), [`command.room.delete.accepted`](#commandroomdeleteaccepted), [`room.deleted`](#roomdeleted)) and room playback events are not associated with a `session_uuid`. They have no ordering guarantee relative to session-scoped events.

**Envelope:** Like every webhook, each room event also carries a `timestamp` field (emission time as an RFC 3339 / ISO 8601 UTC string, e.g. `"2026-06-28T14:30:00.000Z"`) in addition to the fields listed below. Treat it as event-time metadata, not as a guaranteed total-order key; room events do not have the per-session delivery-order guarantee. Delivery requests carry W3C trace context in headers. The example payloads omit these envelope fields for brevity.

## room.created

Fired when a new room is created via `POST /api/v1/rooms`.

| Field      | Type   | Description                                                                                     |
| ---------- | ------ | ----------------------------------------------------------------------------------------------- |
| `event`    | string | Always `"room.created"`. **Required**                                                           |
| `app_uuid` | string | Gateway-minted identifier (`app_<uuid-v4>`) of the application that owns the room. **Required** |
| `room_id`  | string | Unique room identifier. **Required**                                                            |

```json
{
  "event": "room.created",
  "app_uuid": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000"
}
```

## command.room.delete.accepted

Fired immediately when a room delete is requested via `DELETE /api/v1/rooms/{room_id}`. The delete endpoint returns `202 Accepted` with body `{"room_id": "...", "status": "deleting"}`, acknowledging the request before teardown completes. A later [`room.deleted`](#roomdeleted) event confirms the room has actually been torn down.

| Field     | Type   | Description                                           |
| --------- | ------ | ----------------------------------------------------- |
| `event`   | string | Always `"command.room.delete.accepted"`. **Required** |
| `room_id` | string | Unique room identifier. **Required**                  |

```json
{
  "event": "command.room.delete.accepted",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000"
}
```

## room.deleted

Fired when a room has been torn down following a delete request. It is emitted on confirmation that the room is gone (`reason: "closed"`), or after a timeout if confirmation never arrives (`reason: "timeout"`). The `reason` field is always present and is one of these two values.

| Field      | Type   | Description                                                                                                                                                                            |
| ---------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`    | string | Always `"room.deleted"`. **Required**                                                                                                                                                  |
| `app_uuid` | string | Gateway-minted identifier (`app_<uuid-v4>`) of the application that owned the room. **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
{
  "event": "room.deleted",
  "app_uuid": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "reason": "closed"
}
```

## room.member.joined

Fired when a call joins a room. This event confirms the member has actually joined. The join is asynchronous: [`POST /api/v1/rooms/{room_id}/members`](/api/room-members/add) returns `202 Accepted` with status `"joining"` only as an acknowledgment that the request was accepted, while `room.member.joined` fires later, once the underlying system confirms the member is in the room.

| Field          | Type   | Description                                      |
| -------------- | ------ | ------------------------------------------------ |
| `event`        | string | Always `"room.member.joined"`. **Required**      |
| `session_uuid` | string | Session UUID of the joining member. **Required** |
| `room_id`      | string | Room identifier. **Required**                    |

Room membership is identified by `session_uuid`; the caller number is available on the inbound `call.created` or outbound `command.call.dial.accepted` webhook for that session.

```json
{
  "event": "room.member.joined",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000"
}
```

## room.member.left

Fired when a call leaves a room, whether by [`DELETE /api/v1/rooms/{room_id}/members/{uuid}`](/api/room-members/remove), being kicked, or hanging up.

| Field          | Type   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `event`        | string | Always `"room.member.left"`. **Required**                                                                                                                                                                                                                                                                                                                                                                                                  |
| `session_uuid` | string | Session UUID of the departing member. **Required**                                                                                                                                                                                                                                                                                                                                                                                         |
| `room_id`      | string | Room identifier. **Required**                                                                                                                                                                                                                                                                                                                                                                                                              |
| `reason`       | string | Departure trigger. One of: `"hangup"` (member's channel hung up), `"removed"` (explicit leave/kick via the room leave endpoint), `"orphan"` (the gateway detected that the member's call had already gone away — typically a crash or dropped connection — and removed the now-stale membership; treat this as an abnormal disconnect, not a clean leave). Consumers should accept any string here for forward compatibility. **Required** |

Room membership is identified by `session_uuid`; the caller number is available on the inbound `call.created` or outbound `command.call.dial.accepted` webhook for that session.

```json
{
  "event": "room.member.left",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "reason": "hangup"
}
```

## room.member.muted

Fired when a room member is muted via the `room/mute` command.

| Field          | Type   | Description                                    |
| -------------- | ------ | ---------------------------------------------- |
| `event`        | string | Always `"room.member.muted"`. **Required**     |
| `session_uuid` | string | Session UUID of the muted member. **Required** |
| `room_id`      | string | Room identifier. **Required**                  |

Room membership is identified by `session_uuid`; the caller number is available on the inbound `call.created` or outbound `command.call.dial.accepted` webhook for that session.

```json
{
  "event": "room.member.muted",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000"
}
```

## room.member.unmuted

Fired when a room member is unmuted via the `room/unmute` command.

| Field          | Type   | Description                                      |
| -------------- | ------ | ------------------------------------------------ |
| `event`        | string | Always `"room.member.unmuted"`. **Required**     |
| `session_uuid` | string | Session UUID of the unmuted member. **Required** |
| `room_id`      | string | Room identifier. **Required**                    |

Room membership is identified by `session_uuid`; the caller number is available on the inbound `call.created` or outbound `command.call.dial.accepted` webhook for that session.

```json
{
  "event": "room.member.unmuted",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000"
}
```

## room.member.voice\_activity\_changed

Fired each time a room member's [voice activity detection (VAD)](https://en.wikipedia.org/wiki/Voice_activity_detection) state changes. `talking: true` marks speech onset; `talking: false` marks the return to silence. Expect a high-frequency stream during an active conversation and debounce events on your side.

VAD state is delivered via this webhook. The member object returned by [`GET /api/v1/rooms/{room_id}/members/{uuid}`](/api/room-members/get) contains only `{uuid, muted}`.

| Field          | Type    | Description                                                                     |
| -------------- | ------- | ------------------------------------------------------------------------------- |
| `event`        | string  | Always `"room.member.voice_activity_changed"`. **Required**                     |
| `session_uuid` | string  | Session UUID of the member whose VAD state changed. **Required**                |
| `room_id`      | string  | Room identifier. **Required**                                                   |
| `talking`      | boolean | `true` when the member started talking, `false` when they stopped. **Required** |

Room membership is identified by `session_uuid`; the caller number is available on the inbound `call.created` or outbound `command.call.dial.accepted` webhook for that session.

```json
{
  "event": "room.member.voice_activity_changed",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "talking": true
}
```

## command.room.member.volume.accepted

Fired immediately when a member's volume is changed via the `room/volume` command. This is a per-member session command, distinct from the room-level [`command.room.playback.volume.accepted`](#commandroomplaybackvolumeaccepted) event.

| Field          | Type    | Description                                                                                                                                                                                            |
| -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `event`        | string  | Always `"command.room.member.volume.accepted"`. **Required**                                                                                                                                           |
| `session_uuid` | string  | Session UUID of the member whose volume changed. **Required**                                                                                                                                          |
| `room_id`      | string  | Room identifier. **Required**                                                                                                                                                                          |
| `volume`       | integer | New member volume on the `1`–`9` scale (`5` = normal, `1` = quietest, `9` = loudest). `1` is not silence; use mute to silence a member. **Required**                                                   |
| `direction`    | string  | Which side of the member's audio was adjusted. `"out"` means how loudly the member hears the room, `"in"` means how loudly the room hears the member, and `"both"` applies to both sides. **Required** |

Room membership is identified by `session_uuid`; the caller number is available on the inbound `call.created` or outbound `command.call.dial.accepted` webhook for that session.

```json
{
  "event": "command.room.member.volume.accepted",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "volume": 7,
  "direction": "out"
}
```

## command.room.playback.start.accepted

Fired immediately after room playback is dispatched via `POST /api/v1/rooms/{room_id}/playback/play`. This event acknowledges dispatch; it does not prove that audio began. If start is not confirmed within the validation window, a later [`room.playback.failed`](#roomplaybackfailed) event reports `error: "playback_timeout"`.

| Field            | Type      | Description                                                   |
| ---------------- | --------- | ------------------------------------------------------------- |
| `event`          | string    | Always `"command.room.playback.start.accepted"`. **Required** |
| `room_id`        | string    | Room identifier. **Required**                                 |
| `urls`           | string\[] | Array of audio URLs being played. **Required**                |
| `operation_uuid` | string    | Unique operation identifier. **Required**                     |

```json
{
  "event": "command.room.playback.start.accepted",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "urls": ["https://cdn.example.com/audio/announcement.wav"],
  "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123"
}
```

## command.room.playback.pause.accepted

Fired when the room playback pause toggle is accepted and the pause command is
dispatched. The gateway reads the room's prior `playback_status` before
toggling: if it was `"playing"`, this event fires; if it was `"paused"`,
[`command.room.playback.resume.accepted`](#commandroomplaybackresumeaccepted) fires
instead. Neither event independently confirms the resulting media-layer state.

| Field            | Type   | Description                                                   |
| ---------------- | ------ | ------------------------------------------------------------- |
| `event`          | string | Always `"command.room.playback.pause.accepted"`. **Required** |
| `room_id`        | string | Room identifier. **Required**                                 |
| `operation_uuid` | string | Unique operation identifier. **Required**                     |

```json
{
  "event": "command.room.playback.pause.accepted",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123"
}
```

## command.room.playback.resume.accepted

Fired when the room playback pause toggle is accepted and a resume command is
dispatched while the prior `playback_status` was `"paused"`. The same endpoint
emits either [`command.room.playback.pause.accepted`](#commandroomplaybackpauseaccepted)
or this event depending on prior state; it does not independently confirm the
resulting media-layer state.

| Field            | Type   | Description                                                    |
| ---------------- | ------ | -------------------------------------------------------------- |
| `event`          | string | Always `"command.room.playback.resume.accepted"`. **Required** |
| `room_id`        | string | Room identifier. **Required**                                  |
| `operation_uuid` | string | Unique operation identifier. **Required**                      |

```json
{
  "event": "command.room.playback.resume.accepted",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "operation_uuid": "e5f6a7b8-c9d0-4234-9678-9abcdef01234"
}
```

## command.room.playback.stop.accepted

Fired when a room playback stop command is accepted and dispatched. It does not
independently confirm that the media layer has stopped. Its `operation_uuid`
identifies the stop request; the resulting
[`room.playback.ended`](#roomplaybackended) event retains the operation UUID of
the original play request.

| Field            | Type   | Description                                                  |
| ---------------- | ------ | ------------------------------------------------------------ |
| `event`          | string | Always `"command.room.playback.stop.accepted"`. **Required** |
| `room_id`        | string | Room identifier. **Required**                                |
| `operation_uuid` | string | Unique operation identifier. **Required**                    |

```json
{
  "event": "command.room.playback.stop.accepted",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123"
}
```

## command.room.playback.volume.accepted

Fired when a room playback volume-change command is accepted and dispatched.
The `volume` field is the requested target; the event does not independently
confirm that the media layer applied it.

| Field            | Type    | Description                                                                                                                                                                                                                                                                             |
| ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`          | string  | Always `"command.room.playback.volume.accepted"`. **Required**                                                                                                                                                                                                                          |
| `room_id`        | string  | Room identifier. **Required**                                                                                                                                                                                                                                                           |
| `volume`         | integer | New room playback volume on an absolute `0`–`200` scale, where `100` is the baseline (unity) level — the file played at its original loudness — `0` is muted, and values above `100` boost up to a maximum of `200`. The value is the target level, not a relative change. **Required** |
| `operation_uuid` | string  | Unique operation identifier. **Required**                                                                                                                                                                                                                                               |

```json
{
  "event": "command.room.playback.volume.accepted",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "volume": 120,
  "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123"
}
```

## room.playback.ended

Terminal event fired exactly once when room playback completes naturally or is stopped through the API. A multi-source playlist emits one event after every source completes, rather than one event per source. Playback failures and start-confirmation timeouts emit `room.playback.failed` instead.

| Field            | Type      | Description                                                                                                                                                                                                       |
| ---------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`          | string    | Always `"room.playback.ended"`. **Required**                                                                                                                                                                      |
| `room_id`        | string    | Room identifier. **Required**                                                                                                                                                                                     |
| `operation_uuid` | string    | Operation identifier of the originating [`command.room.playback.start.accepted`](#commandroomplaybackstartaccepted) request. An API stop does not replace it with the stop command's operation UUID. **Required** |
| `urls`           | string\[] | The complete original `urls` playlist from the room playback request, in request order. **Required**                                                                                                              |
| `reason`         | string    | Why playback ended: `"completed"` when every source finished naturally, or `"stopped"` when interrupted through the API. **Required**                                                                             |

```json
{
  "event": "room.playback.ended",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123",
  "urls": ["https://cdn.example.com/audio/announcement.wav"],
  "reason": "completed"
}
```

## room.playback.failed

Fired when room playback fails: either the platform reports a playback failure, any source in a multi-source playlist fails, or the gateway does not receive confirmation within 10 seconds that a dispatched play actually started. A failure of any source fails the whole operation. A playback that is confirmed within that window does not fail merely because it runs for more than 10 seconds. The 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    | Operation identifier of the originating [`command.room.playback.start.accepted`](#commandroomplaybackstartaccepted) request. May be empty if state was cleared before this event fired. **Required**                                            |
| `urls`           | string\[] | The complete original `urls` playlist from the room playback request, in request order. **Required**                                                                                                                                            |
| `error`          | string    | Coarse-grained error bucket. One of `"playback_error"` (the platform reported a playback failure) or `"playback_timeout"` (the gateway's playback watchdog timed out; normally because start was not confirmed within 10 seconds). **Required** |

```json
{
  "event": "room.playback.failed",
  "room_id": "acW68-room-550e8400-e29b-41d4-a716-446655440000",
  "operation_uuid": "d4e5f6a7-b8c9-4123-8567-89abcdef0123",
  "urls": ["https://cdn.example.com/audio/announcement.wav"],
  "error": "playback_timeout"
}
```

## Triggered by

These events are produced by the following endpoints:

* [`room.created`](#roomcreated) — [POST /rooms](/api/rooms/create).
* [`command.room.delete.accepted`](#commandroomdeleteaccepted) — [DELETE /rooms/\{room\_id}](/api/rooms/delete) (fires immediately; endpoint returns `202 Accepted`).
* [`room.deleted`](#roomdeleted) — automatic: fires on confirmed teardown after a [`command.room.delete.accepted`](#commandroomdeleteaccepted) (`reason: "closed"`), or with `reason: "timeout"` if confirmation never arrives.
* [`room.member.joined`](#roommemberjoined) — [POST /rooms/\{room\_id}/members](/api/room-members/add) (asynchronous).
* [`room.member.left`](#roommemberleft) — [DELETE /rooms/\{room\_id}/members/\{uuid}](/api/room-members/remove), or automatic on hangup.
* [`room.member.muted`](#roommembermuted) — [room/mute](/api/room-members/mute).
* [`room.member.unmuted`](#roommemberunmuted) — [room/unmute](/api/room-members/unmute).
* [`room.member.voice_activity_changed`](#roommembervoice_activity_changed) — automatic: VAD state change.
* [`command.room.member.volume.accepted`](#commandroommembervolumeaccepted) — [room/volume](/api/room-members/volume) (immediate).
* [`command.room.playback.start.accepted`](#commandroomplaybackstartaccepted) — [POST /rooms/\{room\_id}/playback/play](/api/room-playback/play).
* [`command.room.playback.pause.accepted`](#commandroomplaybackpauseaccepted) — [POST /rooms/\{room\_id}/playback/pause](/api/room-playback/pause) (when prior state was `playing`).
* [`command.room.playback.resume.accepted`](#commandroomplaybackresumeaccepted) — [POST /rooms/\{room\_id}/playback/pause](/api/room-playback/pause) (when prior state was `paused`).
* [`command.room.playback.stop.accepted`](#commandroomplaybackstopaccepted) — [POST /rooms/\{room\_id}/playback/stop](/api/room-playback/stop).
* [`command.room.playback.volume.accepted`](#commandroomplaybackvolumeaccepted) — [POST /rooms/\{room\_id}/playback/volume](/api/room-playback/volume).
* [`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.