Setup Zoom Websockets
AnthropicOfficialCreated by ZoomDistributed by Anthropic
- Skill
- Tool integration
- Automation and integrations
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a reference for integrating Zoom WebSockets to receive real-time event notifications. It covers the architectural differences between WebSockets and webhooks, setup requirements via the Zoom Marketplace, and implementation details for connection management, including authentication, keep-alive heartbeats, and reconnection strategies.
What it does
- Explains the technical trade-offs between Zoom WebSockets and webhooks.
- Provides a step-by-step guide to configuring WebSocket subscriptions in the Zoom Marketplace.
- Includes code patterns for authenticating via Server-to-Server OAuth.
- Demonstrates how to establish and maintain a persistent WebSocket connection.
- Outlines best practices for connection heartbeats and automatic reconnection logic.
- Lists common event types available through the WebSocket stream.
When to use it
- When your application requires real-time, low-latency event updates.
- When security requirements prevent the use of public webhook endpoints.
- When you need to avoid the overhead of individual HTTP requests for every event.
- When you require a stateful, bidirectional communication channel with Zoom.
Advantages
- Lower latency compared to traditional webhook polling or push models.
- Eliminates the need to expose and secure public-facing webhook endpoints.
- Provides a persistent, stateful connection for reliable event delivery.
- Supports secure, direct communication between your server and Zoom.
Usage examples
- “How do I connect to Zoom WebSockets?”
- The skill provides a Node.js implementation using the 'ws' library, showing how to fetch an S2S OAuth token and initialize the WebSocket connection.
- “Should I use webhooks or WebSockets for Zoom events?”
- The skill includes a comparison table highlighting that WebSockets are better for low-latency and high-security needs, while webhooks are simpler for basic event notifications.
- “How do I keep my Zoom WebSocket connection alive?”
- The skill provides a code snippet for implementing a 30-second heartbeat ping to prevent Zoom from closing idle connections.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/anthropics/setup-zoom-websockets/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip setup-zoom-websockets.zip -d .agents/skills
Documentation by Zoom
/setup-zoom-websockets
Background reference for persistent Zoom event streams. Prefer workflow routing first, then use this file when WebSockets are plausibly better than webhooks.
WebSockets vs Webhooks
| Aspect | WebSockets | Webhooks |
|---|---|---|
| Connection | Persistent, bidirectional | One-time HTTP POST |
| Latency | Lower (no HTTP overhead) | Higher (new connection per event) |
| Security | Direct connection, no exposed endpoint | Requires endpoint validation, IP whitelisting |
| Model | Pull (you connect to Zoom) | Push (Zoom connects to you) |
| State | Stateful (maintains connection) | Stateless (each event independent) |
| Setup | More complex (access token, connection) | Simpler (just endpoint URL) |
Choose WebSockets when:
Real-time, low-latency updates are critical
Security is paramount (banking, healthcare, finance)
You don't want to expose a public endpoint
You need bidirectional communication
Choose Webhooks when:
Simpler setup is preferred
Small number of event notifications
Existing HTTP infrastructure
Prerequisites
Server-to-Server OAuth app in Zoom Marketplace
Account ID, Client ID, and Client Secret
WebSocket subscription with events enabled
Need help with S2S OAuth? See the zoom-oauth skill for complete authentication flows.
Start troubleshooting fast: Use the 5-Minute Runbook before deep debugging.
Quick Start
1. Create Server-to-Server OAuth App
Go to Zoom Marketplace
Create a Server-to-Server OAuth app
Copy Account ID, Client ID, Client Secret
2. Enable WebSocket Subscription
In your app, go to Feature → Event Subscriptions
Add an Event Subscription
Select WebSockets as the method type
Select events to subscribe to (e.g.,
meeting.created,meeting.started)Save - an endpoint URL will be generated
3. Connect via WebSocket
const WebSocket = require('ws');
const axios = require('axios');
// Step 1: Get access token
async function getAccessToken() {
const credentials = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');
const response = await axios.post(
'https://zoom.us/oauth/token',
new URLSearchParams({
grant_type: 'account_credentials',
account_id: ACCOUNT_ID
}),
{
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
return response.data.access_token;
}
// Step 2: Connect to WebSocket
async function connectWebSocket() {
const accessToken = await getAccessToken();
// WebSocket URL from your subscription settings
const wsUrl = `wss://ws.zoom.us/ws?subscriptionId=${SUBSCRIPTION_ID}&access_token=${accessToken}`;
const ws = new WebSocket(wsUrl);
ws.on('open', () => {
console.log('WebSocket connection established');
});
ws.on('message', (data) => {
const event = JSON.parse(data);
console.log('Event received:', event.event);
// Handle different event types
switch (event.event) {
case 'meeting.started':
console.log(`Meeting started: ${event.payload.object.topic}`);
break;
case 'meeting.ended':
console.log(`Meeting ended: ${event.payload.object.uuid}`);
break;
case 'meeting.participant_joined':
console.log(`Participant joined: ${event.payload.object.participant.user_name}`);
break;
}
});
ws.on('close', (code, reason) => {
console.log(`Connection closed: ${code} - ${reason}`);
// Implement reconnection logic
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
return ws;
}
connectWebSocket();Event Format
Events received via WebSocket have the same format as webhook events:
{
"event": "meeting.started",
"event_ts": 1706123456789,
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"host_id": "xyz789",
"topic": "Team Standup",
"type": 2,
"start_time": "2024-01-25T10:00:00Z",
"timezone": "America/Los_Angeles"
}
}
}Common Events
| Event | Description |
|---|---|
meeting.created | Meeting scheduled |
meeting.updated | Meeting settings changed |
meeting.deleted | Meeting deleted |
meeting.started | Meeting begins |
meeting.ended | Meeting ends |
meeting.participant_joined | Participant joins meeting |
meeting.participant_left | Participant leaves meeting |
recording.completed | Cloud recording ready |
user.created | New user added |
user.updated | User details changed |
Connection Management
Keep-Alive
WebSocket connections require periodic heartbeats. Zoom will close idle connections.
// Send ping every 30 seconds
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
}, 30000);Reconnection
Implement automatic reconnection for reliability:
function connectWithReconnect() {
const ws = connectWebSocket();
ws.on('close', () => {
console.log('Connection lost. Reconnecting in 5 seconds...');
setTimeout(connectWithReconnect, 5000);
});
return ws;
}Single Connection Limit
Important: Only ONE WebSocket connection can be open per subscription at a time. Opening a new connection will close the existing one.
Detailed References
references/connection.md - Connection lifecycle, authentication, error handling
references/events.md - Complete event types reference
Troubleshooting
troubleshooting/common-issues.md - Subscription URL confusion, disconnects, no-events debugging
Sample Repositories
Official / Community
| Type | Repository | Description |
|---|---|---|
| Node.js | just-zoomit/zoom-websockets | WebSocket sample with S2S OAuth |
WebSockets vs RTMS
Don't confuse WebSockets with RTMS (Realtime Media Streams):
| Feature | WebSockets | RTMS |
|---|---|---|
| Purpose | Event notifications | Media streams |
| Data | Meeting events, user events | Audio, video, transcripts |
| Use case | React to Zoom events | AI/ML, live transcription |
| Skill | This skill | rtms |
For real-time audio/video/transcript data, use the rtms skill instead.
Resources
WebSockets docs: https://developers.zoom.us/docs/api/websockets/
Webhooks comparison: https://www.zoom.com/en/blog/a-guide-to-webhooks-and-websockets/
Developer forum: https://devforum.zoom.us/
Environment Variables
See references/environment-variables.md for standardized
.envkeys and where to find each value.
Files
- ATTRIBUTION.mdAdded by AutoGineer987 B
- LICENSELicence file added by AutoGineer1.1 KiB
- RUNBOOK.md2.9 KiB
- SKILL.md7.4 KiB
- references/connection.md10.4 KiB
- references/environment-variables.md827 B
- references/events.md10.5 KiB
- troubleshooting/common-issues.md1.1 KiB
references/events.md
WebSockets - Event Types
Complete reference for events available via Zoom WebSockets.
Event Structure
All WebSocket events follow this structure:
{
"event": "event.type",
"event_ts": 1706123456789,
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
// Event-specific data
}
}
}| Field | Type | Description |
|---|---|---|
event | string | Event type identifier |
event_ts | number | Unix timestamp (milliseconds) |
payload.account_id | string | Zoom account ID |
payload.object | object | Event-specific payload |
Meeting Events
meeting.created
Triggered when a meeting is scheduled.
{
"event": "meeting.created",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"host_id": "xyz789",
"topic": "Weekly Team Sync",
"type": 2,
"start_time": "2024-01-25T10:00:00Z",
"duration": 60,
"timezone": "America/Los_Angeles"
}
}
}meeting.updated
Triggered when meeting settings are changed.
{
"event": "meeting.updated",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"topic": "Updated: Weekly Team Sync"
},
"old_object": {
"topic": "Weekly Team Sync"
}
}
}meeting.deleted
Triggered when a meeting is deleted.
{
"event": "meeting.deleted",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"host_id": "xyz789"
}
}
}meeting.started
Triggered when a meeting begins.
{
"event": "meeting.started",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"host_id": "xyz789",
"topic": "Weekly Team Sync",
"type": 2,
"start_time": "2024-01-25T10:00:00Z",
"timezone": "America/Los_Angeles"
}
}
}meeting.ended
Triggered when a meeting ends.
{
"event": "meeting.ended",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"host_id": "xyz789",
"topic": "Weekly Team Sync",
"start_time": "2024-01-25T10:00:00Z",
"end_time": "2024-01-25T11:05:00Z",
"duration": 65
}
}
}meeting.participant_joined
Triggered when a participant joins the meeting.
{
"event": "meeting.participant_joined",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"host_id": "xyz789",
"participant": {
"id": "participant123",
"user_id": "user456",
"user_name": "John Doe",
"email": "[email protected]",
"join_time": "2024-01-25T10:02:00Z"
}
}
}
}meeting.participant_left
Triggered when a participant leaves the meeting.
{
"event": "meeting.participant_left",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"participant": {
"id": "participant123",
"user_name": "John Doe",
"leave_time": "2024-01-25T10:45:00Z",
"leave_reason": "left the meeting"
}
}
}
}meeting.sharing_started
Triggered when screen sharing begins.
{
"event": "meeting.sharing_started",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"participant": {
"id": "participant123",
"user_name": "John Doe"
},
"sharing_details": {
"content": "screen",
"date_time": "2024-01-25T10:15:00Z"
}
}
}
}meeting.sharing_ended
Triggered when screen sharing ends.
{
"event": "meeting.sharing_ended",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"participant": {
"id": "participant123",
"user_name": "John Doe"
}
}
}
}Recording Events
recording.started
Triggered when cloud recording starts.
{
"event": "recording.started",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"host_id": "xyz789",
"topic": "Weekly Team Sync",
"start_time": "2024-01-25T10:00:00Z",
"recording_start": "2024-01-25T10:01:00Z"
}
}
}recording.stopped
Triggered when cloud recording stops (paused or ended).
{
"event": "recording.stopped",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"recording_start": "2024-01-25T10:01:00Z",
"recording_end": "2024-01-25T11:00:00Z"
}
}
}recording.completed
Triggered when cloud recording is processed and ready for download.
{
"event": "recording.completed",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 1234567890,
"uuid": "abcdefgh-1234-5678-abcd-1234567890ab",
"host_id": "xyz789",
"topic": "Weekly Team Sync",
"start_time": "2024-01-25T10:00:00Z",
"duration": 60,
"total_size": 157286400,
"recording_count": 2,
"recording_files": [
{
"id": "file123",
"meeting_id": "abcdefgh-1234-5678-abcd-1234567890ab",
"recording_start": "2024-01-25T10:01:00Z",
"recording_end": "2024-01-25T11:00:00Z",
"file_type": "MP4",
"file_size": 104857600,
"download_url": "https://zoom.us/rec/download/...",
"status": "completed"
},
{
"id": "file124",
"file_type": "TRANSCRIPT",
"file_size": 52428800,
"download_url": "https://zoom.us/rec/download/..."
}
]
}
}
}recording.trashed
Triggered when recording is moved to trash.
recording.deleted
Triggered when recording is permanently deleted.
recording.recovered
Triggered when recording is restored from trash.
User Events
user.created
Triggered when a new user is added to the account.
{
"event": "user.created",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": "user789",
"first_name": "Jane",
"last_name": "Smith",
"email": "[email protected]",
"type": 2,
"created_at": "2024-01-25T09:00:00Z"
}
}
}user.updated
Triggered when user details are changed.
{
"event": "user.updated",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": "user789",
"first_name": "Jane",
"last_name": "Smith-Jones"
},
"old_object": {
"last_name": "Smith"
}
}
}user.deleted
Triggered when a user is removed from the account.
{
"event": "user.deleted",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": "user789",
"email": "[email protected]"
}
}
}user.deactivated
Triggered when a user is deactivated.
user.activated
Triggered when a user is activated.
Webinar Events
webinar.created
webinar.updated
webinar.deleted
webinar.started
webinar.ended
webinar.registration_created
Triggered when someone registers for a webinar.
{
"event": "webinar.registration_created",
"payload": {
"account_id": "abcD3ojkdbjfg",
"object": {
"id": 9876543210,
"uuid": "webinar-uuid-here",
"registrant": {
"id": "registrant123",
"email": "[email protected]",
"first_name": "Attendee",
"last_name": "User",
"join_url": "https://zoom.us/w/..."
}
}
}
}Event Handling Example
const eventHandlers = {
// Meeting events
'meeting.created': (payload) => {
console.log(`New meeting: ${payload.object.topic}`);
notifyCalendarService(payload.object);
},
'meeting.started': (payload) => {
console.log(`Meeting started: ${payload.object.topic}`);
updateMeetingStatus(payload.object.id, 'in_progress');
},
'meeting.ended': (payload) => {
console.log(`Meeting ended: ${payload.object.uuid}`);
updateMeetingStatus(payload.object.id, 'completed');
calculateAttendance(payload.object);
},
'meeting.participant_joined': (payload) => {
const { participant } = payload.object;
console.log(`${participant.user_name} joined`);
trackAttendance(payload.object.id, participant);
},
// Recording events
'recording.completed': (payload) => {
console.log(`Recording ready: ${payload.object.topic}`);
downloadRecordings(payload.object.recording_files);
},
// User events
'user.created': (payload) => {
console.log(`New user: ${payload.object.email}`);
sendWelcomeEmail(payload.object);
}
};
ws.on('message', (data) => {
const event = JSON.parse(data);
const handler = eventHandlers[event.event];
if (handler) {
handler(event.payload);
} else {
console.log(`Unhandled event: ${event.event}`);
}
});Subscribing to Events
Configure which events to receive in your Zoom Marketplace app:
Go to Feature → Event Subscriptions
Select WebSockets as method type
Check the events you want to receive
Save the subscription
Note: You can modify subscriptions at any time. Changes take effect immediately.
Event Filtering
If you're receiving too many events, consider:
Subscribe selectively - Only subscribe to events you need
Filter in handler - Drop events that don't match your criteria
Use multiple subscriptions - Route different events to different handlers
// Filter example: Only process events for specific hosts
ws.on('message', (data) => {
const event = JSON.parse(data);
// Only process meetings from specific hosts
const allowedHosts = ['[email protected]', '[email protected]'];
if (event.event.startsWith('meeting.') &&
event.payload.object.host_id &&
!allowedHosts.includes(getHostEmail(event.payload.object.host_id))) {
return; // Skip this event
}
processEvent(event);
});Resources
Event reference: https://developers.zoom.us/docs/api/rest/reference/zoom-api/events/
WebSockets docs: https://developers.zoom.us/docs/api/websockets/
Requirements and compatibility
Written for: Claude Code, Claude apps
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Zoom
- Publisher
- Anthropic (Official)
- Distributed by
- Anthropic
- Source
- anthropics/knowledge-work-plugins · partner-built/zoom-plugin/skills/websockets · commit e050c2e
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from partner-built/zoom-plugin/LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Zoom or Anthropic.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download setup-zoom-websockets.zip