setup-zoom-websockets

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

/setup-zoom-websockets

/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.
持久化Zoom事件流的背景参考文档。优先考虑工作流路由,当WebSockets明显优于webhooks时,再使用本文件。

WebSockets vs Webhooks

WebSockets 与 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
对比维度WebSocketsWebhooks
连接方式持久化、双向单次HTTP POST请求
延迟更低(无HTTP开销)更高(每个事件需新建连接)
安全性直接连接,无暴露端点需要端点验证、IP白名单
交互模式拉取(主动连接Zoom)推送(Zoom主动连接您的服务)
状态性有状态(维持连接)无状态(每个事件独立)
搭建复杂度更复杂(需访问令牌、连接配置)更简单(仅需端点URL)
选择WebSockets的场景:
  • 实时、低延迟更新至关重要
  • 安全性要求极高(如银行、医疗、金融领域)
  • 不想暴露公共端点
  • 需要双向通信
选择Webhooks的场景:
  • 更倾向于简单搭建
  • 仅需少量事件通知
  • 已有HTTP基础设施

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.
  • Zoom Marketplace中创建的Server-to-Server OAuth应用
  • Account ID、Client ID和Client Secret
  • 已启用事件的WebSocket订阅
需要S2S OAuth相关帮助? 请查看**zoom-oauth**指南获取完整的认证流程。
快速开始排查问题: 在深入调试前,请先使用**5分钟运行手册**。

Quick Start

快速开始

1. Create Server-to-Server OAuth App

1. 创建Server-to-Server OAuth应用

  1. Go to Zoom Marketplace
  2. Create a Server-to-Server OAuth app
  3. Copy Account ID, Client ID, Client Secret
  1. 访问Zoom Marketplace
  2. 创建一个Server-to-Server OAuth应用
  3. 复制Account ID、Client ID和Client Secret

2. Enable WebSocket Subscription

2. 启用WebSocket订阅

  1. In your app, go to FeatureEvent 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
  1. 在您的应用中,进入功能事件订阅
  2. 添加事件订阅
  3. 选择WebSockets作为方式类型
  4. 选择要订阅的事件(如
    meeting.created
    meeting.started
  5. 保存后系统将生成一个端点URL

3. Connect via WebSocket

3. 通过WebSocket连接

javascript
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();
javascript
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:
json
{
  "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"
    }
  }
}
通过WebSocket接收的事件与webhook事件格式相同:
json
{
  "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.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
事件描述
meeting.created
会议已预约
meeting.updated
会议设置已修改
meeting.deleted
会议已删除
meeting.started
会议已开始
meeting.ended
会议已结束
meeting.participant_joined
参会者已加入
meeting.participant_left
参会者已离开
recording.completed
云录制已完成
user.created
新用户已添加
user.updated
用户信息已修改

Connection Management

连接管理

Keep-Alive

心跳机制

WebSocket connections require periodic heartbeats. Zoom will close idle connections.
javascript
// Send ping every 30 seconds
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.ping();
  }
}, 30000);
WebSocket连接需要定期发送心跳包,Zoom会关闭空闲连接。
javascript
// Send ping every 30 seconds
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.ping();
  }
}, 30000);

Reconnection

重连机制

Implement automatic reconnection for reliability:
javascript
function connectWithReconnect() {
  const ws = connectWebSocket();
  
  ws.on('close', () => {
    console.log('Connection lost. Reconnecting in 5 seconds...');
    setTimeout(connectWithReconnect, 5000);
  });
  
  return ws;
}
为保证可靠性,请实现自动重连:
javascript
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.
重要提示: 每个订阅同一时间仅能保持一个WebSocket连接。新建连接会关闭现有连接。

Detailed References

详细参考文档

  • references/connection.md - Connection lifecycle, authentication, error handling
  • references/events.md - Complete event types reference
  • references/connection.md - 连接生命周期、认证、错误处理
  • references/events.md - 完整事件类型参考

Troubleshooting

故障排查

  • troubleshooting/common-issues.md - Subscription URL confusion, disconnects, no-events debugging
  • troubleshooting/common-issues.md - 订阅URL混淆、连接断开、无事件接收等问题排查

Sample Repositories

示例仓库

Official / Community

官方/社区

TypeRepositoryDescription
Node.jsjust-zoomit/zoom-websocketsWebSocket sample with S2S OAuth
类型仓库描述
Node.jsjust-zoomit/zoom-websockets包含S2S OAuth的WebSocket示例

WebSockets vs RTMS

WebSockets 与 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.
请勿混淆WebSockets与RTMS(实时媒体流):
特性WebSocketsRTMS
用途事件通知媒体流传输
数据类型会议事件、用户事件音频、视频、转录文本
适用场景响应Zoom事件AI/ML、实时转录
对应指南本指南rtms
如需实时音频/视频/转录文本数据,请使用rtms指南。

Resources

资源

Environment Variables

环境变量

  • See references/environment-variables.md for standardized
    .env
    keys and where to find each value.
  • 请查看references/environment-variables.md获取标准化的
    .env
    键名及各值的获取位置。