File: C:/Users/fred/.codex/.tmp/plugins/plugins/zoom/skills/meeting-sdk/references/webinars.md
# Meeting SDK - Webinars
Embed webinar experiences using Meeting SDK.
## Overview
Meeting SDK can join webinars, not just meetings. This guide covers webinar-specific features and considerations.
## Webinar vs Meeting
| 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 |
## Joining Webinars
### Web SDK
```javascript
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: '[email protected]', // Required for registration
passWord: password,
tk: registrantToken, // If registration required
});
```
### Registration Token
If the webinar requires registration:
```javascript
// 1. Register via API first
const response = await axios.post(
`https://api.zoom.us/v2/webinars/${webinarId}/registrants`,
{
email: '[email protected]',
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,
});
```
### Native SDKs (iOS/Android/Windows)
```swift
// iOS - Join webinar
let joinParam = ZoomSDKJoinWebinarParam()
joinParam.webinarNumber = webinarNumber
joinParam.userName = "Attendee"
joinParam.userEmail = "[email protected]"
joinParam.webinarPassword = password
joinParam.webinarToken = registrantToken // If registered
meetingService.joinWebinar(with: joinParam)
```
```kotlin
// Android - Join webinar
val joinParams = JoinMeetingParams().apply {
meetingNo = webinarId
displayName = "Attendee"
password = webinarPassword
}
// Webinar uses same joinMeeting API
meetingService.joinMeetingWithParams(context, joinParams, JoinMeetingOptions())
```
## Attendee vs Panelist
### Role Detection
```javascript
// 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');
}
});
```
### Attendee Limitations
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 |
## Q&A Feature
### Web SDK Q&A
```javascript
// 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);
});
```
### Native SDK Q&A
```swift
// 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
### Web SDK
```javascript
// 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`);
}
});
```
### Native SDK
```swift
// iOS
let webinarController = meetingService.getWebinarController()
webinarController?.raiseHand()
webinarController?.lowerHand()
```
## Promote/Demote Attendees
Hosts can promote attendees to panelists:
```javascript
// 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);
```
## Polling
### Web SDK Polling
```javascript
// 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');
});
```
## Practice Session
Before a webinar starts, hosts can run a practice session:
```javascript
// 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 in Webinars
Chat permissions differ in webinars:
```javascript
// 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!');
```
## Webinar-Specific Events
```javascript
// 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);
});
```
## Best Practices
### For Large Webinars
1. **Disable attendee video** - Already default for webinars
2. **Use Q&A instead of chat** - More organized for large audiences
3. **Pre-register attendees** - Better tracking and control
4. **Test with practice session** - Verify setup before going live
### UI Considerations
```javascript
// 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();
}
}
```
## Resources
- **Webinar API**: https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#tag/Webinars
- **Meeting SDK Webinar**: https://developers.zoom.us/docs/meeting-sdk/web/webinar/
- **Developer Forum**: https://devforum.zoom.us/