# Handle participant events The Meeting SDK for web client view emits events when participants join, leave, or change state (for example, mute and unmute or host and co-host promotion). Subscribe to these events with [`ZoomMtg.inMeetingServiceListener()`](https://marketplacefront.zoom.us/sdk/meeting/web/functions/ZoomMtg.inMeetingServiceListener.html), then call the participant list APIs to refresh your UI. ## When to use participant events Use participant events when your app renders any UI that depends on who is in the meeting or their state. Common cases include: - Showing a custom participant roster outside the client view. - Updating mute, video, host, or co-host indicators on your own UI elements. - Reacting to a participant being moved to or from the waiting room. - Tracking the active speaker for analytics or custom layouts. > **Not necessary for default UI** > > If you only need the default Zoom UI, the client view already updates itself. You do not need to subscribe to these events. ## Participant events Register a callback for each event with `ZoomMtg.inMeetingServiceListener()`. Call this inside the `ZoomMtg.init()` success callback, before calling `ZoomMtg.join()`, so you do not miss the first events. > **Duplicate registration** > > Each call to `ZoomMtg.inMeetingServiceListener()` for the same event name adds a callback. It does not replace the previous one. Register each event only once to avoid running your handler multiple times. | Event | When it fires | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `onUserJoin` | A participant joins the meeting or webinar. | | `onUserLeave` | A participant leaves. The payload includes a `reasonCode` (for example, host ended the meeting, user left, or user was removed). | | `onUserUpdate` | A participant's state changes (mute, video, host or co-host role, raise hand, and so on). | | `onUserIsInWaitingRoom` | A participant enters or leaves the waiting room. | | `onActiveSpeaker` | The active speaker changes. The payload is an array of `{ userId, userName }`. | For the full list of supported events, see the [`inMeetingServiceListener` reference](https://marketplacefront.zoom.us/sdk/meeting/web/functions/ZoomMtg.inMeetingServiceListener.html). ## Refresh the participant list When you receive an event, treat it as a signal to refresh your local copy of participant state rather than reading the payload alone. The SDK is the source of truth, and polling the participant list APIs on each event keeps your UI consistent without holding per-user state in your app. Use these functions to read current state: - `ZoomMtg.getAttendeeslist()` - returns the list of participants currently in the meeting. In webinars, returns panelists only. - `ZoomMtg.getCurrentUser()` - returns the local user, including role and media state. > **Performance** > > Refresh on event, not on a timer. The events fire only when state changes, so reading the list inside the callback avoids unnecessary work and keeps your UI in sync with the SDK. ## Example: refresh UI on participant state changes The following example subscribes to the participant events, then refreshes a custom roster and the mute and host indicators whenever participants change. ```javascript ZoomMtg.init({ leaveUrl: leaveUrl, success: () => { // Register before join() so you do not miss early events. ZoomMtg.inMeetingServiceListener("onUserJoin", (data) => { refreshRoster(); }); ZoomMtg.inMeetingServiceListener("onUserLeave", (data) => { // data.reasonCode indicates why the participant left. refreshRoster(); }); ZoomMtg.inMeetingServiceListener("onUserUpdate", (data) => { refreshRoster(); }); ZoomMtg.inMeetingServiceListener("onActiveSpeaker", (data) => { // data: [{ userId, userName }] highlightActiveSpeaker(data[0]); }); ZoomMtg.join({ signature: signature, meetingNumber: meetingNumber, passWord: passWord, userName: userName, success: () => console.log("joined"), error: (error) => console.log(error), }); }, error: (error) => console.log(error), }); function refreshRoster() { const participants = ZoomMtg.getAttendeeslist(); const me = ZoomMtg.getCurrentUser(); // Re-render your custom UI with the latest participant state. // For example, update mute icons, host badges, or your participant list. renderParticipantList(participants, me); } ``` ## Best practices - Subscribe to events inside the `ZoomMtg.init()` success callback, before calling `ZoomMtg.join()`, so you do not miss early events. - Re-read state from `getAttendeeslist()` or `getCurrentUser()` inside the callback. Do not cache a parallel participant list in your own app. - Register each event only once. Multiple registrations for the same event name are cumulative, not replacements. - Keep callbacks lightweight. If you need to do heavy work (network requests, large renders), debounce or schedule it outside the callback. - Unsubscribe is not required. The SDK clears all listeners when the meeting ends.