Waiting Room

Last updated Oct 19, 2026 Prerequisites

This page assumes you've already initialized the SDK and understand the meeting object structure. Refer to Initialize SDK and Meeting Object Explained if needed.

The waiting room feature allows hosts to control who can join a meeting. When enabled, participants must wait for approval before entering the meeting.

WebMobile

ReactWeb ComponentsAngular

How the Waiting Room Works

After you call meeting.join(), one of two events will occur:

  • roomJoined - You are allowed to join the meeting immediately
  • waitlisted - You are placed in the waiting room and must wait for host approval

Use meeting.self.roomState to track the user's state in the meeting.

Note

The diagram below represents only waiting room-related states. The roomState property also transitions through other states like 'disconnected', 'left', 'kicked', and 'ended'.

Waiting Room States

State Flow

        join()
          ↓
    [waitlisted]  ←------ (host rejects)
          ↓                     ↓
   (host accepts)           [rejected]
          ↓
      [joined]

Listening to State Changes

Joined Event

Triggered when the local user successfully joins the meeting.

Monitor when the local user joins the meeting:

import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react";
import { useEffect } from "react";

function MeetingStatus() {
    const roomState = useRealtimeKitSelector((m) => m.self.roomState);
    const joined = roomState === "joined";

useEffect(() => {
        if (joined) {
            console.log("Successfully joined the meeting");
        }
    }, [joined]);

return joined ? <div>You are in the meeting</div> : null;
}

Alternatively, use event listeners:

import { useRealtimeKitClient } from "@cloudflare/realtimekit-react";

useEffect(() => {
    if (!meeting) return;

const handleRoomJoined = () => {
        console.log("Successfully joined the meeting");
    };

meeting.self.on("roomJoined", handleRoomJoined);

return () => {
        meeting.self.off("roomJoined", handleRoomJoined);
    };
}, [meeting]);
meeting.self.on("roomJoined", () => {
    // Local user is in the meeting
    console.log("Successfully joined the meeting");
});
meeting.addMeetingRoomEventListener(object : RtkMeetingRoomEventListener {
    override fun onMeetingRoomJoinCompleted(meeting: RealtimeKitClient) {
        // Local user is in the meeting
    }
})
extension MeetingViewModel: RtkMeetingRoomEventListener {
    func onMeetingRoomJoinCompleted(meeting: RealtimeKitClient) {
        // Local user is in the meeting
    }
}

Monitor when the local user joins the meeting:

import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react-native";
import { useEffect } from "react";

function MeetingStatus() {
    const roomState = useRealtimeKitSelector((m) => m.self.roomState);
    const joined = roomState === "joined";

useEffect(() => {
        if (joined) {
            console.log("Successfully joined the meeting");
        }
    }, [joined]);

return joined ? <Text>You are in the meeting</Text> : null;
}

Alternatively, use event listeners:

import { useRealtimeKitClient } from "@cloudflare/realtimekit-react-native";

useEffect(() => {
    if (!meeting) return;

const handleRoomJoined = () => {
        console.log("Successfully joined the meeting");
    };

meeting.self.on("roomJoined", handleRoomJoined);

return () => {
        meeting.self.off("roomJoined", handleRoomJoined);
    };
}, [meeting]);

Waitlisted Event

Triggered when the local user is placed in the waiting room.

Monitor when the local user is in the waiting room:

function WaitingRoomStatus() {
    const roomState = useRealtimeKitSelector((m) => m.self.roomState);
    const isWaitlisted = roomState === "waitlisted";

useEffect(() => {
        if (isWaitlisted) {
            console.log("You are in the waiting room");
        }
    }, [isWaitlisted]);

return isWaitlisted ? <div>Waiting for host approval...</div> : null;
}

Alternatively, use event listeners:

useEffect(() => {
    if (!meeting) return;

const handleWaitlisted = () => {
        console.log("You are in the waiting room");
    };

meeting.self.on("waitlisted", handleWaitlisted);

return () => {
        meeting.self.off("waitlisted", handleWaitlisted);
    };
}, [meeting]);
meeting.self.on("waitlisted", () => {
    // Local user is waitlisted
    console.log("You are in the waiting room. Waiting for host approval...");
});
meeting.addSelfEventListener(object : RtkSelfEventListener {
    override fun onWaitListStatusUpdate(waitListStatus: WaitListStatus) {
        when (waitListStatus) {
            WAITING -> {
                // Local user is in the waiting room
            }
            REJECTED -> {
                // Local user's join room request was rejected by the host
            }
            NONE, ACCEPTED -> {
                // Local user is not in the wait list or was already accepted
            }
        }
    }
})
extension MeetingViewModel: RtkSelfEventListener {
    func onWaitListStatusUpdate(waitListStatus: WaitListStatus) {
        switch waitListStatus {
        case .accepted:
            // Local user's join room request was accepted by the host
        case .waiting:
            // Local user is in the waiting room
        case .rejected:
            // Local user's join room request was rejected by the host
        default:
            return .none
        }
    }
}

Monitor when the local user is in the waiting room:

function WaitingRoomStatus() {
    const roomState = useRealtimeKitSelector((m) => m.self.roomState);
    const isWaitlisted = roomState === "waitlisted";

useEffect(() => {
        if (isWaitlisted) {
            console.log("You are in the waiting room");
        }
    }, [isWaitlisted]);

return isWaitlisted ? <Text>Waiting for host approval...</Text> : null;
}

Alternatively, use event listeners:

useEffect(() => {
    if (!meeting) return;

const handleWaitlisted = () => {
        console.log("You are in the waiting room");
    };

meeting.self.on("waitlisted", handleWaitlisted);

return () => {
        meeting.self.off("waitlisted", handleWaitlisted);
    };
}, [meeting]);

Rejected Event

Triggered when the host rejects the entry request.

Monitor when the host rejects the entry request:

function RejectionStatus() {
    const roomState = useRealtimeKitSelector((m) => m.self.roomState);
    const rejected = roomState === "rejected";

useEffect(() => {
        if (rejected) {
            console.log("Your entry request was rejected");
        }
    }, [rejected]);

return rejected ? <div>Your entry was rejected by the host</div> : null;
}

Alternatively, use event listeners:

useEffect(() => {
    if (!meeting) return;

const handleRoomLeft = ({ state }) => {
        if (state === "rejected") {
            console.log("Your entry request was rejected");
        }
    };

meeting.self.on("roomLeft", handleRoomLeft);

return () => {
        meeting.self.off("roomLeft", handleRoomLeft);
    };
}, [meeting]);
meeting.self.on("roomLeft", ({ state }) => {
    if (state === "rejected") {
        // Host rejected the entry
        console.log("Your entry request was rejected");
    }
});

When the host rejects the entry request, the onWaitListStatusUpdate callback is triggered with WaitListStatus.REJECTED:

meeting.addSelfEventListener(object : RtkSelfEventListener {
    override fun onWaitListStatusUpdate(waitListStatus: WaitListStatus) {
        when (waitListStatus) {
            WaitListStatus.REJECTED -> {
                // Local user's join room request was rejected by the host
                Log.d("WaitingRoom", "Your entry request was rejected")
            }
            WaitListStatus.WAITING -> {
                // Local user is in the waiting room
            }
            WaitListStatus.ACCEPTED, WaitListStatus.NONE -> {
                // Local user was accepted or not in waitlist
            }
        }
    }
})

When the host rejects the entry request, the onWaitListStatusUpdate callback is triggered with WaitListStatus.rejected:

extension MeetingViewModel: RtkSelfEventListener {
    func onWaitListStatusUpdate(waitListStatus: WaitListStatus) {
        switch waitListStatus {
        case .rejected:
            // Local user's join room request was rejected by the host
            print("Your entry request was rejected")
        case .waiting:
            // Local user is in the waiting room
            break
        case .accepted:
            // Local user's join room request was accepted by the host
            break
        default:
            break
        }
    }
}

Monitor when the host rejects the entry request:

function RejectionStatus() {
    const roomState = useRealtimeKitSelector((m) => m.self.roomState);
    const rejected = roomState === "rejected";

useEffect(() => {
        if (rejected) {
            console.log("Your entry request was rejected");
        }
    }, [rejected]);

return rejected ? <Text>Your entry was rejected by the host</Text> : null;
}

Alternatively, use event listeners:

useEffect(() => {
    if (!meeting) return;

const handleRoomLeft = ({ state }) => {
        if (state === "rejected") {
            console.log("Your entry request was rejected");
        }
    };

meeting.self.on("roomLeft", handleRoomLeft);

return () => {
        meeting.self.off("roomLeft", handleRoomLeft);
    };
}, [meeting]);

Monitor State with roomState

You can also directly check the current room state.

Handle all waiting room states in one component:

function WaitingRoomManager() {
    const roomState = useRealtimeKitSelector((m) => m.self.roomState);

switch (roomState) {
        case "init":
            return <div>Connecting...</div>;
        case "waitlisted":
            return <div>Waiting for host approval...</div>;
        case "joined":
            return <div>You are in the meeting</div>;
        case "rejected":
            return <div>Your entry was rejected</div>;
        case "left":
            return <div>You left the meeting</div>;
        case "kicked":
            return <div>You were removed from the meeting</div>;
        case "ended":
            return <div>The meeting has ended</div>;
        case "disconnected":
            return <div>Connection lost</div>;
        default:
            return null;
    }
}