| name | video-sdk/web |
| description | Guidance for building browser-based sessions with Zoom Video SDK for Web (@zoom/videosdk v2.4.5) in React, Vue, Angular, Svelte, or vanilla TypeScript. Use for session lifecycle, audio/video rendering, screen sharing, chat/file transfer, recording, subsessions, live and broadcast streaming, incoming RTMP, RTMS controls, whiteboard, voice translation, custom processors, video masks, PTZ, stats, WebAssembly/SharedArrayBuffer, CSP/COOP/COEP, JWT session tokens, and SDK error debugging. Prefer over generic WebRTC advice whenever @zoom/videosdk is involved. |
| triggers | ["video sdk web","custom video web","attachvideo","web videosdk","zoom videosdk web","@zoom/videosdk","video web session","peer-video-state-change","video rendering"] |
Zoom Video SDK Web (v2.4.5)
Current npm/changelog release verified on 2026-07-10. Version 2.4.5 adds incoming RTMP
ingestion and customAudioConstraints, improves share rendering, AMD environments, and virtual
backgrounds on lower-performance devices, and restores the missing userKey field.
Expert guidance for building video sessions with Zoom Video SDK for Web.
This skill is for custom video sessions, not embedded Zoom meetings.
If the user wants a custom UI for a real Zoom meeting, route to ../../meeting-sdk/web/component-view/SKILL.md.
Use ../../probe-sdk/SKILL.md as an optional browser/device/network readiness gate before client.join(...).
SDK Sample Package
The reviewed zoom-video-sdk-web-2.4.5.zip archive is the react-video-sdk-demo sample application, not the @zoom/videosdk library distribution. It contains no bundled SKILL.md or paired API-reference files.
- Use this skill as the primary workflow and API-routing guide.
- Use references/sample-app-2.4.5.md for source-backed implementation patterns from the sample.
- Treat installed
@zoom/videosdk TypeScript declarations and official API documentation as authoritative when the sample and types disagree.
- Never ship the sample's client-side SDK Secret/JWT generator. Generate Video SDK JWTs on a backend.
Quick Start
Installation
bun install @zoom/videosdk --save
npm install @zoom/videosdk --save
Basic Session Setup
import ZoomVideo from "@zoom/videosdk";
const client = ZoomVideo.createClient();
await client.init("en-US", "Global", { patchJsMedia: true });
await client.join(sessionName, jwtToken, userName, sessionPassword);
const stream = client.getMediaStream();
Core API Reference
ZoomVideo (Static Methods)
| Method | Description |
|---|
createClient() | Create VideoClient instance (singleton) |
checkSystemRequirements() | Check browser compatibility → { audio, video, screen } |
checkFeatureRequirements() | Get supported/unsupported features list |
getDevices(skipPermissionCheck?) | Enumerate media devices |
createLocalAudioTrack(deviceId?) | Create local audio track for preview |
createLocalVideoTrack(deviceId?) | Create local video track for preview |
destroyClient() | Destroy client instance |
preloadDependentAssets(path?) | Preload WebAssembly/Worker assets |
VideoClient Methods
Session Management
await client.init(language, dependentAssets, options?);
await client.join(topic, token, userName, password?, idleTimeoutMins?);
await client.leave(end?);
User Management
client.getCurrentUserInfo(): Participant;
client.getAllUser(): Participant[];
client.getUser(userId): Participant | undefined;
client.getSessionHost(): Participant | undefined;
client.makeHost(userId);
client.makeManager(userId);
client.revokeManager(userId);
client.removeUser(userId);
client.changeName(name, userId?);
Feature Clients
client.getMediaStream();
client.getChatClient();
client.getCommandClient();
client.getRecordingClient();
client.getSubsessionClient();
client.getLiveTranscriptionClient();
client.getLiveStreamClient();
client.getWhiteboardClient();
client.getBroadcastStreamingClient();
client.getRealTimeMediaStreamsClient();
client.getVoiceTranslatorClient();
client.getIncomingLiveStreamClient();
MediaStream (Audio/Video Control)
Audio
const stream = client.getMediaStream();
await stream.startAudio({
mute?: boolean,
speakerOnly?: boolean,
backgroundNoiseSuppression?: boolean,
microphoneId?: string,
speakerId?: string,
});
await stream.muteAudio();
await stream.unmuteAudio();
stream.stopAudio();
stream.getMicList(): MediaDevice[];
stream.getSpeakerList(): MediaDevice[];
await stream.switchMicrophone(deviceId);
await stream.switchSpeaker(deviceId);
Video
await stream.startVideo();
await stream.startVideo({
cameraId?: string,
hd?: boolean,
fullHd?: boolean,
mirrored?: boolean,
virtualBackground?: { imageUrl: string | 'blur' | undefined, cropped?: boolean },
});
const videoElement = await stream.attachVideo(userId, VideoQuality.Video_720P);
container.appendChild(videoElement);
await stream.stopVideo();
stream.detachVideo(userId);
stream.getCameraList(): MediaDevice[];
await stream.switchCamera(deviceId);
Screen Share
await stream.startShareScreen({
broadcastToSubsession: boolean,
optimizedForSharedVideo: boolean,
secondaryCameraId: string,
});
await stream.stopShareScreen();
await stream.startShareView(canvas, userId);
stream.stopShareView();
Events
client.on("connection-change", (payload) => {
});
client.on("user-added", (participants: Participant[]) => {});
client.on("user-updated", (participants: Participant[]) => {});
client.on("user-removed", (participants: Participant[]) => {});
client.on("current-audio-change", (payload) => {
});
client.on("active-speaker", (payload) => {
});
client.on("video-active-change", (payload) => {
});
client.on("peer-video-state-change", (payload) => {
});
client.on("active-share-change", (payload) => {
});
client.on("peer-share-state-change", (payload) => {
});
client.on("chat-on-message", (payload) => {
});
client.on("device-change", () => {
});
Framework-Specific Implementation Guides
For complete implementation examples with full project setup, hooks/composables/services, and components:
Quick Setup Requirements
All frameworks MUST configure:
- COOP/COEP Headers (for SharedArrayBuffer support):
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
}
- TypeScript Custom Elements (for video rendering):
declare namespace JSX {
interface IntrinsicElements {
"video-player-container": React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement>,
HTMLElement
>;
}
}
Core Implementation Pattern
All implementations should follow this pattern:
- Client Management - Singleton client with init/join/leave lifecycle
- Media Stream - Audio/video/screen share controls with state sync
- Participants - User list with video state change listeners
- Video Rendering - Use
video-player-container + attachVideo()
- Event Handling - Connection, audio, video, chat events
- Error Handling - Map SDK error codes to user messages
Custom UI Responsibility
The Video SDK Web package provides media/session APIs, not a complete meeting toolbar. In custom or PureJS samples, implement these controls explicitly:
- Device selectors: populate
stream.getMicList(), stream.getSpeakerList(), and stream.getCameraList() after permissions are available; refresh on device-change and device-permission-change; switch with switchMicrophone, switchSpeaker, and switchCamera.
- Camera effects: check
stream.isSupportVirtualBackground() before offering blur; apply blur with startVideo({ virtualBackground: { imageUrl: 'blur' } }) or stream.updateVirtualBackgroundImage('blur').
- Statistics: subscribe with
subscribeAudioStatisticData, subscribeVideoStatisticData, and subscribeShareStatisticData; render audio-statistic-data-change, video-statistic-data-change, and share-statistic-data-change payloads or poll get*StatisticData().
- Participant media reconciliation: after join and after
user-added, user-updated, user-removed, and peer-video-state-change, call client.getAllUser() and render/detach users based on bVideoOn. Do not rely on events alone for users who were already in the session.
- Screen share reconciliation: after join and on
active-share-change, peer-share-state-change, share-content-change, and passively-stop-share, check stream.getActiveShareUserId() / stream.getShareUserList() and attach or detach share views yourself.
- Recording/captions: wire toolbar buttons to
client.getRecordingClient() and client.getLiveTranscriptionClient(); handle host/account privilege failures and update UI from recording-change, caption-message, caption-enable, and caption-host-disable.
JWT Token Requirements
Generate JWT on your server using HMAC SHA256. Never expose SDK secret in client code.
Required Claims
{
app_key: 'YOUR_SDK_KEY',
tpc: 'session-name',
role_type: 1,
version: 1,
iat: Math.floor(Date.now() / 1000) - 30,
exp: iat + 7200,
}
Optional Claims
| Claim | Description |
|---|
user_key | Unique user identifier (max 36 chars) |
session_key | Session identifier for cloud recording (max 36 chars) |
geo_regions | Data center regions: AU,BR,CA,DE,HK,IN,JP,CN,MX,NL,SG,US |
cloud_recording_option | 0 = combined video, 1 = separate files per user |
cloud_recording_election | 1 to record user's self-view |
cloud_recording_transcript_option | 0 = none, 1 = transcript, 2 = transcript + summary |
video_webrtc_mode | 0 = disable, 1 = enable WebRTC video |
audio_webrtc_mode | 1 = enable WebRTC audio (Web only) |
telemetry_tracking_id | For Web SDK telemetry tracking |
Node.js(jsrsasign) Quick Start JWT Generator Service:
git clone https://github.com/zoom/videosdk-auth-endpoint-sample.git
cd videosdk-auth-endpoint-sample
bun install
npm install
mv .env.example .env
bun run start
npm run start
jsrsasign Example Code:
import { KJUR } from "jsrsasign";
const iat = Math.floor(Date.now() / 1000) - 30;
const exp = iat + 60 * 60 * 2;
const payload = {
app_key: process.env.ZOOM_SDK_KEY,
tpc: "session-name",
role_type: 1,
version: 1,
video_webrtc_mode: 1,
audio_webrtc_mode: 1,
iat,
exp,
};
const token = KJUR.jws.JWS.sign(
"HS256",
JSON.stringify({ alg: "HS256", typ: "JWT" }),
JSON.stringify(payload),
process.env.ZOOM_SDK_SECRET
);
Init Options
interface InitOptions {
webEndpoint?: string;
enforceMultipleVideos?: boolean | { disableRenderLimits?: boolean };
enforceVirtualBackground?: boolean;
stayAwake?: boolean;
leaveOnPageUnload?: boolean;
patchJsMedia?: boolean;
alternativeNameForVideoPlayer?: string;
}
Video Quality Enum
enum VideoQuality {
Video_90P = 0,
Video_180P = 1,
Video_360P = 2,
Video_720P = 3,
Video_1080P = 4,
}
Best Practices
- Always check browser support before initializing
- Generate JWT on server - never expose SDK secret in client
- Handle connection events for robust session management
- Request user gesture before starting audio (browser requirement)
- Use
patchJsMedia: true for latest WebAssembly fixes
- Set
leaveOnPageUnload: true for clean disconnection
- Implement error handling for all async operations
- Clean up event listeners on session end
Common Issues
| Issue | Solution |
|---|
| Audio doesn't start | Requires user gesture (click/tap) |
| Video not rendering | Use video-player-container custom element and append attachVideo() result |
| Video briefly appears then goes black/hidden | Remove old canvas/avatar/name overlays; the returned video-player must be the visible tile |
| Late join misses remote video/share | Reconcile getAllUser(), getShareUserList(), and active share state after join and media events |
| Device/blur/stats controls missing | Build toolbar controls manually with MediaStream device, virtual background, and statistic APIs |
| JWT invalid | Verify tpc matches session name |
| SharedArrayBuffer error | MUST add COOP/COEP headers in vite.config.ts |
OPERATION_TIMEOUT on startVideo | Check camera permissions and ensure COOP/COEP headers are set |
INVALID_PARAMETERS on attachVideo | Don't pass container as 3rd param - append the returned element instead |
| Self-video not showing | Pass currentUserVideoOn prop to VideoGrid (participant.bVideoOn may lag) |
| Participants not updating | Listen for video-active-change and peer-video-state-change events |
| "Waiting for participants" stuck | Pass isJoined to useParticipants hook and listen for connection-change |
Detailed Guides
Core Concepts (read these first — they apply to every feature)
Feature References
- references/sessions.md - Session lifecycle, user roles, JWT token, connection events
- references/audio.md - Audio controls, devices, muting, dial-out, noise suppression
- references/video.md - Video capture, rendering, virtual background, PTZ cameras
- references/screen-share.md - Screen sharing, annotation, tab audio, share privileges
- references/features.md - Preview, recording, subsessions, live stream, command channel, transcription, PSTN, SIP, quality stats
- references/advanced.md - Chat, custom processors, whiteboard
- references/sample-app-2.4.5.md - Reviewed React/Vite sample map: RTMS, incoming RTMP, broadcast viewing, whiteboard, voice translation, processors, and video masks
- references/browser-support.md - Browser compatibility, SharedArrayBuffer, CSP headers
- references/error-codes.md - Complete error code reference
- troubleshooting/common-issues.md - Troubleshooting guide: symptoms → causes → fixes for the most common Video SDK bugs
Type Definitions Location
All TypeScript definitions are in @zoom/videosdk/dist/types:
index.d.ts - Main exports
zoomvideo.d.ts - ZoomVideo namespace
videoclient.d.ts - VideoClient methods and events
media.d.ts - MediaStream, audio/video options
common.d.ts - Shared types (Participant, enums)
chat.d.ts - Chat client
recording.d.ts - Recording client
subsession.d.ts - Breakout rooms
broadcast-streaming.d.ts - Broadcast host/viewer APIs
real-time-media-streams.d.ts - RTMS lifecycle client
event-callback.d.ts - Event names and payloads
Resources Homepage
Official Docs URLs
# Main Documentation
https://developers.zoom.us/docs/video-sdk/web/get-started/
https://developers.zoom.us/docs/video-sdk/web/sessions/
https://developers.zoom.us/docs/video-sdk/web/event-handling/
# Audio & Video
https://developers.zoom.us/docs/video-sdk/web/video/
https://developers.zoom.us/docs/video-sdk/web/audio/
https://developers.zoom.us/docs/video-sdk/web/preview/
# Screen Sharing
https://developers.zoom.us/docs/video-sdk/web/share/
https://developers.zoom.us/docs/video-sdk/web/share-annotation/
https://developers.zoom.us/docs/video-sdk/web/share-browser-options/
# Advanced Features
https://developers.zoom.us/docs/video-sdk/web/recording/
https://developers.zoom.us/docs/video-sdk/web/subsessions/
https://developers.zoom.us/docs/video-sdk/web/live-stream/
https://developers.zoom.us/docs/video-sdk/web/command-channel/
https://developers.zoom.us/docs/video-sdk/web/transcription-translation/
https://developers.zoom.us/docs/video-sdk/web/chat/
# Phone Integration
https://developers.zoom.us/docs/video-sdk/web/pstn/
https://developers.zoom.us/docs/video-sdk/web/sip/
# Quality & Compatibility
https://developers.zoom.us/docs/video-sdk/web/quality/
https://developers.zoom.us/docs/video-sdk/web/browser-support/
https://developers.zoom.us/docs/video-sdk/web/sharedarraybuffer/
https://developers.zoom.us/docs/video-sdk/web/error-codes/
# Other
https://developers.zoom.us/docs/video-sdk/web/virtual-background/
https://developers.zoom.us/docs/video-sdk/web/frameworks/
Repo-Local Appendices
The documents above are primarily based on the incoming zoom-videosdk-web skill. The files below remain in this repo as complementary material for deeper examples, compatibility shims, and operational debugging.
Operational Docs
- RUNBOOK.md - 5-minute preflight checks before deep debugging
- MAINTENANCE.md - upstream/source-maintenance notes for this imported skill
Compatibility References
Complementary Examples