Handle participant events
The Meeting SDK for web component view emits events when participants join, leave, or change state (for example, mute amd unmute or host and co-host promotion). Subscribe to these events on the embedded client, 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 embedded client.
- Updating mute, video, host, or co-host indicators on your own UI elements.
- Reacting to participant state changes.
- Tracking the active speaker for analytics or custom layouts.
Not necessary for default UI
If you only need the default Zoom UI, the component view already updates itself. You do not need to subscribe to these events.
Participant events
Register a callback for each event with client.on() inside the .then() callback of client.init(), before calling client.join(), so you do not miss the first events.
Duplicate registration
The
clientinstance persists across rejoin. Register listeners once when you first create the client, not inside eachinit()call, so you do not accumulate duplicate handlers.
| Event | When it fires |
|---|---|
user-added | A participant joins the meeting or webinar. |
user-removed | A participant leaves. The payload includes a reason indicating whether the participant left, was removed, or the meeting ended. |
user-updated | A participant's state changes (mute, video, host or co-host role, raise hand, and so on). |
active-speaker | The active speaker changes. The payload is an array of { userId, displayName }. |
For the full list of supported events and payload shapes, see the component view SDK reference.
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 on the client to read current state:
client.getAttendeeslist()- returns the list of participants currently in the meeting. In webinars, returns panelists only.client.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 participant events, then refreshes a custom roster and the mute and host indicators whenever participants change.
client.init({ zoomAppRoot: meetingSDKElement, language: "en-US" }).then(() => {
// Register before join() so you do not miss early events.
client.on("user-added", (payload) => {
refreshRoster();
});
client.on("user-removed", (payload) => {
// payload includes the reason the participant left.
refreshRoster();
});
client.on("user-updated", (payload) => {
refreshRoster();
});
client.on("active-speaker", (payload) => {
// payload: [{ userId, displayName }]
highlightActiveSpeaker(payload[0]);
});
client.join({
signature: signature,
meetingNumber: meetingNumber,
password: password,
userName: userName,
});
});
function refreshRoster() {
const participants = client.getAttendeeslist();
const me = client.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
- Register inside the
.then()callback ofclient.init(), before callingclient.join(), so you do not miss early events. - Re-read state from
getAttendeeslist()orgetCurrentUser()inside the callback. Do not cache a parallel participant list in your own app. - The
clientinstance persists across rejoin. Register listeners once when you first create the client, not inside eachinit()call, to avoid duplicate subscriptions. - Keep callbacks lightweight. If you need to do heavy work (network requests, large renders), debounce or schedule it outside the callback.