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, andVIDEO_CHAT_POLICY_UNAVAILABLEare 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, andVIDEO_WAITING_ROOM_DENY_FAILEDare returned to the host when one of the host’s waiting-room operations fails. AVIDEO_WAITING_ROOM_DENY_FAILEDis 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 arePOST /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:
- 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_HELDcan simply re-send after the admission lands, and the samelivekit_tokennow passes the lobby gate. - 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.
- 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.
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_tokenon 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 withVIDEO_CHAT_GUEST_FORBIDDENand no retry helps — rejoin from a fresh join link so the page loads a current invite token. - Never reuse a
join_tokenacross 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_FORBIDDENwith 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(orVIDEO_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_UNAVAILABLEandVIDEO_CHAT_POLICY_UNAVAILABLEfail 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:request_id— frommeta.request_idon the refused response.- Room id — the UUID in the route path.
join_token, redacted — first and last four characters only; never paste a full invite token into a ticket.- Timestamp in UTC of the refusal, and whether 503s repeated under backoff.
- The error code verbatim, and which side saw it — a guest chat refusal or a host waiting-room operation.
- Lobby state — whether the waiting room was armed, whether
auto_promotewas set, and whether the host had joined when the refusal fired.
See also
- Video guest chat refusals —
the fail-closed tenant-resolution, lobby-admission, and policy-read gates
on
POST /api/v1/video/rooms/:id/chat, with worked request/response envelopes. - Video room consoles — the host-side lobby panel: arming the waiting room, admitting, and denying.
- Video room lifecycle failures — join-token mint refusals and capacity gates ahead of the lobby.
- Error codes reference — the full
VIDEO_*code families.