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
| 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
| 对比维度 | WebSockets | Webhooks |
|---|---|---|
| 连接方式 | 持久化、双向 | 单次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应用
- Go to Zoom Marketplace
- Create a Server-to-Server OAuth app
- Copy Account ID, Client ID, Client Secret
- 访问Zoom Marketplace
- 创建一个Server-to-Server OAuth应用
- 复制Account ID、Client ID和Client Secret
2. Enable WebSocket Subscription
2. 启用WebSocket订阅
- 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
- 在您的应用中,进入功能 → 事件订阅
- 添加事件订阅
- 选择WebSockets作为方式类型
- 选择要订阅的事件(如、
meeting.created)meeting.started - 保存后系统将生成一个端点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
常见事件
| Event | Description |
|---|---|
| Meeting scheduled |
| Meeting settings changed |
| Meeting deleted |
| Meeting begins |
| Meeting ends |
| Participant joins meeting |
| Participant leaves meeting |
| Cloud recording ready |
| New user added |
| User details changed |
| 事件 | 描述 |
|---|---|
| 会议已预约 |
| 会议设置已修改 |
| 会议已删除 |
| 会议已开始 |
| 会议已结束 |
| 参会者已加入 |
| 参会者已离开 |
| 云录制已完成 |
| 新用户已添加 |
| 用户信息已修改 |
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
官方/社区
| Type | Repository | Description |
|---|---|---|
| Node.js | just-zoomit/zoom-websockets | WebSocket sample with S2S OAuth |
| 类型 | 仓库 | 描述 |
|---|---|---|
| Node.js | just-zoomit/zoom-websockets | 包含S2S OAuth的WebSocket示例 |
WebSockets vs RTMS
WebSockets 与 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.
请勿混淆WebSockets与RTMS(实时媒体流):
| 特性 | WebSockets | RTMS |
|---|---|---|
| 用途 | 事件通知 | 媒体流传输 |
| 数据类型 | 会议事件、用户事件 | 音频、视频、转录文本 |
| 适用场景 | 响应Zoom事件 | AI/ML、实时转录 |
| 对应指南 | 本指南 | rtms |
如需实时音频/视频/转录文本数据,请使用rtms指南。
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/
- WebSockets文档: https://developers.zoom.us/docs/api/websockets/
- Webhooks对比: https://www.zoom.com/en/blog/a-guide-to-webhooks-and-websockets/
- 开发者论坛: https://devforum.zoom.us/
Environment Variables
环境变量
- See references/environment-variables.md for standardized keys and where to find each value.
.env
- 请查看references/environment-variables.md获取标准化的键名及各值的获取位置。
.env