Generated banner for Setup Zoom Websockets
Generated logo for Setup Zoom Websockets

Setup Zoom Websockets

AnthropicOfficialCreated by ZoomDistributed by Anthropic

  • Skill
  • Tool integration
  • Automation and integrations
  • MIT
  • No issues found

From the author

Reference skill for Zoom WebSockets. Use after routing to a low-latency event workflow when persistent connections, faster event delivery, or security constraints make WebSockets preferable to webhooks.
— the description by Zoom

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

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/anthropics/setup-zoom-websockets/download
  2. Unzip 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

AspectWebSocketsWebhooks
ConnectionPersistent, bidirectionalOne-time HTTP POST
LatencyLower (no HTTP overhead)Higher (new connection per event)
SecurityDirect connection, no exposed endpointRequires endpoint validation, IP whitelisting
ModelPull (you connect to Zoom)Push (Zoom connects to you)
StateStateful (maintains connection)Stateless (each event independent)
SetupMore 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
  1. Go to Zoom Marketplace

  2. Create a Server-to-Server OAuth app

  3. Copy Account ID, Client ID, Client Secret

2. Enable WebSocket Subscription
  1. In your app, go to Feature → Event Subscriptions

  2. Add an Event Subscription

  3. Select WebSockets as the method type

  4. Select events to subscribe to (e.g., meeting.created, meeting.started)

  5. 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

EventDescription
meeting.createdMeeting scheduled
meeting.updatedMeeting settings changed
meeting.deletedMeeting deleted
meeting.startedMeeting begins
meeting.endedMeeting ends
meeting.participant_joinedParticipant joins meeting
meeting.participant_leftParticipant leaves meeting
recording.completedCloud recording ready
user.createdNew user added
user.updatedUser 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

Troubleshooting

Sample Repositories

Official / Community
TypeRepositoryDescription
Node.jsjust-zoomit/zoom-websocketsWebSocket sample with S2S OAuth

WebSockets vs RTMS

Don't confuse WebSockets with RTMS (Realtime Media Streams):

FeatureWebSocketsRTMS
PurposeEvent notificationsMedia streams
DataMeeting events, user eventsAudio, video, transcripts
Use caseReact to Zoom eventsAI/ML, live transcription
SkillThis skillrtms

For real-time audio/video/transcript data, use the rtms skill instead.

Resources

Environment Variables

Files

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
    }
  }
}
FieldTypeDescription
eventstringEvent type identifier
event_tsnumberUnix timestamp (milliseconds)
payload.account_idstringZoom account ID
payload.objectobjectEvent-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:

  1. Go to Feature → Event Subscriptions

  2. Select WebSockets as method type

  3. Check the events you want to receive

  4. 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:

  1. Subscribe selectively - Only subscribe to events you need

  2. Filter in handler - Drop events that don't match your criteria

  3. 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

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