Setting the file. One moment.
Skill 121 · Build Zoom Meeting SDK App
Subchapter 121.15
references/webinars.mdMarkdown8 KBView on GitHub
Meeting SDK can join webinars, not just meetings. This guide covers webinar-specific features and considerations.
| Feature | Meeting | Webinar |
|---|---|---|
| Max participants | 100-1000 | 500-50,000 |
| Participant roles | Host, co-host, participant | Host, panelist, attendee |
| Attendee video | Yes | No (view-only) |
| Attendee audio | Yes | Raise hand to unmute |
| Q&A | No | Yes |
| Registration | Optional | Common |
| Practice session | No | Yes |
const client = ZoomMtgEmbedded.createClient();
await client.init({
zoomAppRoot: document.getElementById('meetingSDKElement'),
language: 'en-US',
});
// Join webinar (same as meeting)
await client.join({
sdkKey: SDK_KEY,
signature: signature,
meetingNumber: webinarId, // Webinar ID
userName: 'Attendee Name',
userEmail: 'attendee@example.com', // Required for registration
passWord: password,
tk: registrantToken, // If registration required
});If the webinar requires registration:
// 1. Register via API first
const response = await axios.post(
`https://api.zoom.us/v2/webinars/${webinarId}/registrants`,
{
email: 'attendee@example.com',
first_name: 'John',
last_name: 'Doe'
},
{ headers: { 'Authorization': `Bearer ${accessToken}` }}
);
// 2. Get registrant token
const registrantToken = response.data.registrant_id;
// 3. Use token when joining
await client.join({
// ... other params
tk: registrantToken,
});// iOS - Join webinar
let joinParam = ZoomSDKJoinWebinarParam()
joinParam.webinarNumber = webinarNumber
joinParam.userName = "Attendee"
joinParam.userEmail = "attendee@example.com"
joinParam.webinarPassword = password
joinParam.webinarToken = registrantToken // If registered
meetingService.joinWebinar(with: joinParam)// Android - Join webinar
val joinParams = JoinMeetingParams().apply {
meetingNo = webinarId
displayName = "Attendee"
password = webinarPassword
}
// Webinar uses same joinMeeting API
meetingService.joinMeetingWithParams(context, joinParams, JoinMeetingOptions())// Web SDK
client.on('user-updated', (payload) => {
const { userId } = payload;
const user = client.getUser(userId);
// Check role
if (user.isHost) {
console.log('User is host');
} else if (user.userRole === 'panelist') {
console.log('User is panelist');
} else {
console.log('User is attendee');
}
});Attendees in webinars have restricted capabilities:
| Action | Attendee Can Do? |
|---|---|
| View video | Yes |
| Send video | No |
| Listen to audio | Yes |
| Unmute self | No (must be promoted) |
| Send chat | To panelists/everyone (if enabled) |
| Ask Q&A | Yes |
| Raise hand | Yes |
// Check if Q&A is enabled
const qaClient = client.getQAClient();
const isQAEnabled = qaClient.isQAEnabled();
// Ask a question
await qaClient.askQuestion('What is the pricing?', false); // false = public
// Answer a question (panelist/host only)
await qaClient.answerQuestion(questionId, 'The pricing is...');
// Get all questions
const questions = qaClient.getAllQuestions();
questions.forEach(q => {
console.log(`Q: ${q.text}`);
console.log(`A: ${q.answerText || 'Unanswered'}`);
});
// Q&A events
client.on('question-created', (payload) => {
console.log('New question:', payload.question.text);
});
client.on('answer-created', (payload) => {
console.log('Answer:', payload.answer.text);
});// iOS
let qaController = meetingService.getWebinarQAController()
// Ask question
qaController?.askQuestion("What is the pricing?", isAnonymous: false)
// Get questions (host/panelist)
let questions = qaController?.getAllQuestionList()// Raise hand
client.raiseHand();
// Lower hand
client.lowerHand();
// Check status
const myself = client.getCurrentUser();
console.log('Hand raised:', myself.bRaiseHand);
// Event
client.on('user-updated', (payload) => {
if (payload.bRaiseHand !== undefined) {
console.log(`${payload.userId} ${payload.bRaiseHand ? 'raised' : 'lowered'} hand`);
}
});// iOS
let webinarController = meetingService.getWebinarController()
webinarController?.raiseHand()
webinarController?.lowerHand()Hosts can promote attendees to panelists:
// Web SDK (host only)
const webinarClient = client.getWebinarClient();
// Promote to panelist (allows video/audio)
await webinarClient.promoteAttendee(userId);
// Demote back to attendee
await webinarClient.demoteAttendee(userId);
// Allow attendee to talk (temporary unmute)
await webinarClient.allowAttendeeTalk(userId);
await webinarClient.disallowAttendeeTalk(userId);// Get polling client
const pollingClient = client.getPollingClient();
// Check if polling available
const isAvailable = pollingClient.isPollingEnabled();
// Submit poll answer (attendee)
await pollingClient.submitPollAnswer(pollId, [
{ questionId: 'q1', answerIds: ['a1'] }
]);
// Poll events
client.on('poll-started', (payload) => {
console.log('Poll started:', payload.poll);
displayPoll(payload.poll);
});
client.on('poll-ended', (payload) => {
console.log('Poll ended');
});Before a webinar starts, hosts can run a practice session:
// Detect practice session
client.on('meeting-status-changed', (payload) => {
if (payload.status === 'practice') {
console.log('In practice session - attendees cannot join yet');
} else if (payload.status === 'webinar') {
console.log('Webinar is live');
}
});Chat permissions differ in webinars:
// Get chat privilege
const chatClient = client.getChatClient();
const privilege = chatClient.getPrivilege();
// privilege values:
// 1 = No one
// 2 = Host and panelists only
// 3 = Everyone publicly
// 4 = Everyone publicly and privately
// Send to panelists (attendee)
await chatClient.send('Question about pricing', /* to all panelists */);
// Send to everyone (if allowed)
await chatClient.sendToAll('Hello everyone!');// Web SDK events
client.on('webinar-invite', (payload) => {
// Attendee invited to become panelist
});
client.on('webinar-depromote', (payload) => {
// Demoted from panelist to attendee
});
client.on('webinar-allow-attendee-chat', (payload) => {
// Chat permissions changed
});
client.on('webinar-attendee-status', (payload) => {
// Attendee count updated
console.log('Attendees:', payload.attendeeCount);
});// Adjust UI based on role
function setupUI(role) {
if (role === 'attendee') {
// Hide video controls (can't send video)
hideElement('#videoControls');
// Show Q&A and raise hand
showElement('#qaPanel');
showElement('#raiseHandButton');
// Disable unmute (until promoted)
disableElement('#unmuteButton');
} else if (role === 'panelist' || role === 'host') {
// Full controls
showAllControls();
}
}