mcpField
S
setup-zoom-websockets

setup-zoom-websockets

github.com/anthropics开发33.6k2.1k 次安装1 天前更新
代码仓库

关于 setup-zoom-websockets

面向开发者的参考技能,用于实现持久化 Zoom 事件流。内容说明 WebSockets 何时比 Webhooks 更适合实时双向事件传递,并介绍配置与连接管理。

主要功能

  1. 01比较 WebSockets 与 Webhooks
  2. 02介绍 Server-to-Server OAuth 前提
  3. 03配置事件订阅
  4. 04说明心跳与重连
  5. 05提供 Zoom 事件参考

适用场景

  1. 01接收低延迟会议事件
  2. 02避免公开 Webhook 端点
  3. 03选择 WebSockets 或 Webhooks
  4. 04维护 Zoom 事件连接

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/anthropics/knowledge-work-plugins --skill setup-zoom-websockets

安装后在 Claude Code 中输入/setup-zoom-websockets确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

RUNBOOK.md
references/
connection.md
environment-variables.md
events.md
troubleshooting/
common-issues.md

详细介绍

/setup-zoom-websockets

持久化 Zoom 事件流的背景参考。应先使用工作流路由,只有当 WebSocket 可能优于 Webhook 时,再参考本文件。

WebSocket 与 Webhook 对比

方面WebSocketWebhook
连接持久、双向一次性 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 应用
  1. 前往 Zoom Marketplace
  2. 创建一个 Server-to-Server OAuth 应用
  3. 复制 Account ID、Client ID、Client Secret
2. 启用 WebSocket 订阅
  1. 在你的应用中,前往 Feature → Event Subscriptions
  2. 添加一个 Event Subscription
  3. 选择 WebSockets 作为方法类型
  4. 选择要订阅的事件(例如 meeting.created、meeting.started)
  5. 保存 - 系统将生成一个端点 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 连接。打开新连接会关闭已有连接。

详细参考

故障排查

示例仓库

官方 / 社区
类型仓库说明
Node.jsjust-zoomit/zoom-websockets基于 S2S OAuth 的 WebSocket 示例

WebSockets 与 RTMS

不要混淆 WebSockets 与 RTMS(Realtime Media Streams,实时媒体流):

特性WebSocketsRTMS
用途事件通知媒体流
数据会议事件、用户事件音频、视频、转录文本
使用场景响应 Zoom 事件AI/ML、实时转录
Skill本 Skillrtms

如需获取实时音频、视频或转录文本数据,请改用 rtms Skill。

资源

环境变量

评论

支持 Markdown