Skip to main content

Troubleshooting: video waiting-room and lobby errors — VIDEO_CHAT_LOBBY_HELD, VIDEO_WAITING_ROOM_DENY_FAILED, and the guest refusal path

The waiting room holds every guest outside the meeting until the host admits them. While a guest is held, their chat sends are refused — that is the lobby working as configured, not an outage. This page decodes every code on that path, from the guest’s 403 to the host’s failed deny, and closes with the evidence bundle support needs when a refusal outlives its fix.

What these are — guests, the waiting room, and who an error is about

A guest is a participant with no Orbit account session: they opened an invite join link, landed on the guest-join page, and received a LiveKit session for the room. The Waiting-room hold (chat) glossary entry and the video guest chat refusals runbook cover the chat-send side in depth; this page covers the waiting-room path itself, on both sides of the lobby door. The waiting room is a per-room host control. When it is armed, guests connect into a lobby tier instead of the meeting, and the host admits or denies each pending guest from the room console. Two code families ride this path, and they are about different people:
  • VIDEO_CHAT_LOBBY_*, VIDEO_CHAT_GUEST_FORBIDDEN, and VIDEO_CHAT_POLICY_UNAVAILABLE are returned to the guest (or to an integration posting on the guest’s behalf) when a chat send is refused.
  • VIDEO_WAITING_ROOM_FAILED, VIDEO_WAITING_ROOM_ADMIT_FAILED, and VIDEO_WAITING_ROOM_DENY_FAILED are returned to the host when one of the host’s waiting-room operations fails. A VIDEO_WAITING_ROOM_DENY_FAILED is a server fault while recording the host’s deny — it says nothing about the guest, and the guest’s lobby session is untouched by it.

The decoder — every code on the waiting-room and lobby path

The two 403s differ in their fix: VIDEO_CHAT_LOBBY_HELD clears when the host acts (admit), while VIDEO_CHAT_GUEST_FORBIDDEN clears when the guest re-joins with a token the room recognizes. The two 503s clear on their own; they are the platform failing closed rather than delivering a message it could not screen.

Host-side ladder — admitting and denying from the room console

The host works the lobby from the room view in the developer console; the console walkthrough, including the lobby panel, is in Video room consoles. The underlying calls are POST /api/v1/video/rooms-scheduled/:id/waiting-room/admit/:identity and POST /api/v1/video/rooms-scheduled/:id/waiting-room/deny/:identity. What changes for the guest’s chat sends on each outcome:
  1. Admit promotes the held guest from the lobby tier into the meeting. The admission flips the guest’s live permission in place — no new token is minted for it — so a guest whose send was refused with VIDEO_CHAT_LOBBY_HELD can simply re-send after the admission lands, and the same livekit_token now passes the lobby gate.
  2. Deny terminates the held guest’s session. A denied guest cannot retry their way back in; if the host changes their mind, issue the guest a fresh invite and admit them when they rejoin.
  3. A failed admit or deny (VIDEO_WAITING_ROOM_ADMIT_FAILED / VIDEO_WAITING_ROOM_DENY_FAILED) leaves the lobby exactly as it was. The guest sees nothing from your failed deny; retry the operation before interpreting the guest’s next refusal as a lobby verdict.
If the room is configured with auto_promote, the first host entry lifts every held guest at once — a VIDEO_CHAT_LOBBY_HELD refusal on a room with auto_promote set usually means no host has joined yet, which is the lobby working as configured.

Guest-side moves — tokens that pass and tokens that never will

  • Hold a fresh invite token. The join_token on a guest chat send must match a live invite for the room. If the link is stale, revoked, or hand-edited, the send is refused with VIDEO_CHAT_GUEST_FORBIDDEN and no retry helps — rejoin from a fresh join link so the page loads a current invite token.
  • Never reuse a join_token across rooms. The token is bound to the room that issued it; a send for one room carrying another room’s token matches nothing and is refused for the same reason as a missing token.
  • After admission, re-send first and re-fetch tokens only if the session dropped. An admit changes the guest’s live permission, not their tokens, so the existing session’s chat sends pass as soon as the admission lands. If the tab was reloaded or the connection dropped since the admit, rejoin from the invite link to fetch a fresh LiveKit token and then send.

What never to do

  • Do not retry a VIDEO_CHAT_GUEST_FORBIDDEN with the same payload expecting success. The refusal is deterministic: a token the route cannot match will not match on the tenth retry. Fetch a fresh invite, rejoin, then send.
  • Do not call the room chat endpoint with a stale token. A token older than the guest’s live session, or minted for a different room, is a guaranteed 403; it also masks the real lobby state, because the lobby gate is only reached after the token verifies.
  • Do not read VIDEO_WAITING_ROOM_DENY_FAILED (or VIDEO_WAITING_ROOM_ADMIT_FAILED) as a guest error. Both are host-side operation failures — a server fault while recording the decision — and the guest’s lobby state is unchanged by them.
  • Do not loop a 503 without backoff. VIDEO_CHAT_LOBBY_CHECK_UNAVAILABLE and VIDEO_CHAT_POLICY_UNAVAILABLE fail closed on purpose; a tight retry loop adds load to the degraded layer without clearing it faster.

Escalation evidence — the support bundle

Work the sections above first; most refusals are a stale invite, an un-admitted guest, or a host operation that needs one retry. When a refusal survives a fresh join link and a confirmed host decision, open a ticket with:
  1. request_id — from meta.request_id on the refused response.
  2. Room id — the UUID in the route path.
  3. join_token, redacted — first and last four characters only; never paste a full invite token into a ticket.
  4. Timestamp in UTC of the refusal, and whether 503s repeated under backoff.
  5. The error code verbatim, and which side saw it — a guest chat refusal or a host waiting-room operation.
  6. Lobby state — whether the waiting room was armed, whether auto_promote was set, and whether the host had joined when the refusal fired.

See also