关于 setup-zoom-websockets
面向开发者的参考技能,用于实现持久化 Zoom 事件流。内容说明 WebSockets 何时比 Webhooks 更适合实时双向事件传递,并介绍配置与连接管理。
主要功能
- 01比较 WebSockets 与 Webhooks
- 02介绍 Server-to-Server OAuth 前提
- 03配置事件订阅
- 04说明心跳与重连
- 05提供 Zoom 事件参考
适用场景
- 01接收低延迟会议事件
- 02避免公开 Webhook 端点
- 03选择 WebSockets 或 Webhooks
- 04维护 Zoom 事件连接
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ anthropics/ knowledge-work-plugins --skill setup-zoom-websockets安装后在 Claude Code 中输入/setup-zoom-websockets确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
RUNBOOK.mdreferences/connection.mdenvironment-variables.mdevents.mdtroubleshooting/common-issues.md详细介绍
/setup-zoom-websockets
持久化 Zoom 事件流的背景参考。应先使用工作流路由,只有当 WebSocket 可能优于 Webhook 时,再参考本文件。
WebSocket 与 Webhook 对比
| 方面 | WebSocket | Webhook |
|---|---|---|
| 连接 | 持久、双向 | 一次性 HTTP POST |
| 延迟 | 较低(无 HTTP 开销) | 较高(每个事件新建连接) |
| 安全性 | 直接连接,无需暴露端点 | 需要端点验证、IP 白名单 |
| 模型 | 拉取(你连接到 Zoom) | 推送(Zoom 连接到你) |
| 状态 | 有状态(维护连接) | 无状态(每个事件相互独立) |
| 设置 | 较复杂(访问令牌、连接) | 较简单(仅需端点 URL) |
以下情况选择 WebSocket:
- 实时、低延迟更新至关重要
- 安全性最为重要(银行、医疗、金融)
- 不想暴露公共端点
- 需要双向通信
以下情况选择 Webhook:
- 倾向于更简单的设置
- 事件通知数量较少
- 已有现成的 HTTP 基础设施
前置条件
- 在 Zoom Marketplace 中创建一个 Server-to-Server OAuth 应用
- Account ID、Client ID 和 Client Secret
- 已启用事件的 WebSocket 订阅
需要 S2S OAuth 帮助? 完整的认证流程请参见 zoom-oauth 技能。
快速开始排障: 在深入调试之前,请先使用 5 分钟运行手册。
快速开始
1. 创建 Server-to-Server OAuth 应用
- 前往 Zoom Marketplace
- 创建一个 Server-to-Server OAuth 应用
- 复制 Account ID、Client ID、Client Secret
2. 启用 WebSocket 订阅
- 在你的应用中,前往 Feature → Event Subscriptions
- 添加一个 Event Subscription
- 选择 WebSockets 作为方法类型
- 选择要订阅的事件(例如
meeting.created、meeting.started) - 保存 - 系统将生成一个端点 URL
3. 通过 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();
事件格式
通过 WebSocket 接收的事件与 Webhook 事件格式相同:
{
"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"
}
}
}
常见事件
| 事件 | 说明 |
|---|---|
meeting.created | 会议已安排 |
meeting.updated | 会议设置已更改 |
meeting.deleted | 会议已删除 |
meeting.started | 会议开始 |
meeting.ended | 会议结束 |
meeting.participant_joined | 参会者加入会议 |
meeting.participant_left | 参会者离开会议 |
recording.completed | 云端录制已就绪 |
user.created | 新增用户 |
user.updated | 用户详情已更改 |
连接管理
保活
WebSocket 连接需要定期发送心跳。Zoom 会关闭空闲连接。
// Send ping every 30 seconds
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
}, 30000);
重连
为保证可靠性,请实现自动重连:
function connectWithReconnect() {
const ws = connectWebSocket();
ws.on('close', () => {
console.log('Connection lost. Reconnecting in 5 seconds...');
setTimeout(connectWithReconnect, 5000);
});
return ws;
}
单连接限制
重要: 每个订阅同一时间只能打开一个 WebSocket 连接。打开新连接会关闭已有连接。
详细参考
- references/connection.md - 连接生命周期、认证、错误处理
- references/events.md - 完整事件类型参考
故障排查
- troubleshooting/common-issues.md - 订阅 URL 混淆、断连、无事件调试
示例仓库
官方 / 社区
| 类型 | 仓库 | 说明 |
|---|---|---|
| Node.js | just-zoomit/zoom-websockets | 基于 S2S OAuth 的 WebSocket 示例 |
WebSockets 与 RTMS
不要混淆 WebSockets 与 RTMS(Realtime Media Streams,实时媒体流):
| 特性 | WebSockets | RTMS |
|---|---|---|
| 用途 | 事件通知 | 媒体流 |
| 数据 | 会议事件、用户事件 | 音频、视频、转录文本 |
| 使用场景 | 响应 Zoom 事件 | AI/ML、实时转录 |
| Skill | 本 Skill | rtms |
如需获取实时音频、视频或转录文本数据,请改用 rtms Skill。
资源
- 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/
环境变量
- 标准化
.env键名及各值的获取位置,请参阅 references/environment-variables.md。
评论
支持 Markdown