Files
2026-07-13 12:20:06 +08:00

276 lines
7.4 KiB
Markdown

# RTMS - Webhooks
RTMS-related webhook events and configuration.
## CRITICAL: Respond 200 IMMEDIATELY!
**The #1 cause of random disconnects:**
If your webhook handler takes too long to respond, Zoom assumes failure and retries. The retry creates a second connection, which kicks out your first connection (only 1 connection allowed per stream).
```javascript
// CORRECT: Respond first, process async
app.post('/webhook', (req, res) => {
res.status(200).send(); // IMMEDIATELY!
// Then process asynchronously
handleRTMSEvent(req.body);
});
// WRONG: Processing before responding
app.post('/webhook', async (req, res) => {
await heavyProcessing(req.body); // Zoom may retry while waiting!
res.status(200).send();
});
```
## URL Validation Challenge
When configuring your webhook URL, Zoom sends a validation challenge:
```javascript
app.post('/webhook', (req, res) => {
const { event, payload } = req.body;
// Handle URL validation
if (event === 'endpoint.url_validation') {
const hash = crypto
.createHmac('sha256', process.env.ZOOM_SECRET_TOKEN)
.update(payload.plainToken)
.digest('hex');
return res.json({
plainToken: payload.plainToken,
encryptedToken: hash
});
}
res.status(200).send();
// ... handle other events
});
```
## Events
### meeting.rtms_started
Sent when RTMS stream is ready for a meeting.
```json
{
"event": "meeting.rtms_started",
"payload": {
"account_id": "account_id",
"object": {
"meeting_id": "meeting_id",
"meeting_uuid": "meeting_uuid",
"host_id": "host_user_id",
"rtms_stream_id": "stream_id",
"server_urls": "wss://rtms-sjc1.zoom.us/...",
"signature": "auth_signature"
}
}
}
```
### meeting.rtms_stopped
Sent when RTMS stream ends.
```json
{
"event": "meeting.rtms_stopped",
"payload": {
"account_id": "account_id",
"object": {
"meeting_id": "meeting_id",
"rtms_stream_id": "stream_id"
}
}
}
```
### webinar.rtms_started
Sent when RTMS stream is ready for a webinar.
```json
{
"event": "webinar.rtms_started",
"payload": {
"account_id": "account_id",
"object": {
"meeting_id": "meeting_id",
"meeting_uuid": "meeting_uuid",
"host_id": "host_user_id",
"rtms_stream_id": "stream_id",
"server_urls": "wss://rtms-sjc1.zoom.us/...",
"signature": "auth_signature"
}
}
}
```
> **Important**: Webinar payloads use `meeting_uuid`, NOT `webinar_uuid`. The signature and connection flow are identical to meetings.
**Webinar-specific considerations:**
- **Panelists**: Full audio/video streams are available for panelists.
- **Attendees**: View-only participants; individual streams may not be available.
- **Practice sessions**: Not documented for RTMS.
- **Q&A/Polls**: Not exposed via RTMS.
### webinar.rtms_stopped
Sent when RTMS stream ends for a webinar.
```json
{
"event": "webinar.rtms_stopped",
"payload": {
"account_id": "account_id",
"object": {
"meeting_id": "meeting_id",
"rtms_stream_id": "stream_id"
}
}
}
```
### session.rtms_started
Sent when RTMS stream is ready for a Video SDK session.
```json
{
"event": "session.rtms_started",
"payload": {
"account_id": "account_id",
"object": {
"session_id": "session_id",
"rtms_stream_id": "stream_id",
"server_urls": "wss://rtms-sjc1.zoom.us/...",
"signature": "auth_signature"
}
}
}
```
> **Important**: Video SDK payloads use `session_id` instead of `meeting_uuid`. The HMAC signature must use `session_id` in place of `meeting_uuid`.
**Video SDK-specific considerations:**
- Uses **SDK Key/Secret** (not OAuth Client ID/Secret) for authentication.
- Requires a **Video SDK App** (not a General App) in Zoom Marketplace.
- Once connected, the WebSocket protocol is identical to meetings.
### session.rtms_stopped
Sent when RTMS stream ends for a Video SDK session.
```json
{
"event": "session.rtms_stopped",
"payload": {
"account_id": "account_id",
"object": {
"session_id": "session_id",
"rtms_stream_id": "stream_id"
}
}
}
```
### Screen Share Events (via msg_type 5)
Subscribe to receive `SHARING_START` and `SHARING_STOP` events when participants start/stop screen sharing.
## Payload Fields
| Field | Description |
|-------|-------------|
| `rtms_stream_id` | Unique stream identifier |
| `server_urls` | WebSocket signaling server URL |
| `meeting_uuid` | Meeting unique identifier (needed for signature) |
| `signature` | Pre-computed auth signature (alternative to self-generating) |
## Server URL Geo-Routing
Server URLs contain airport/region codes:
| Code | Location |
|------|----------|
| `sjc` | San Jose, California |
| `iad` | Washington DC |
| `sin` | Singapore |
| `fra` | Frankfurt, Germany |
| `syd` | Sydney, Australia |
```javascript
// Extract region from server URL
const hostname = new URL(serverUrl).hostname; // rtms-sjc1.zoom.us
const region = hostname.split('-')[1].replace(/[0-9]/g, ''); // sjc
```
**Tip**: For production, route webhooks to workers in the same region as the Zoom server.
## Subscribing to RTMS Events
### In Zoom Marketplace (General App - Meetings and Webinars)
1. Go to your app settings
2. Navigate to **Features****Access**
3. **Enable Event Subscription**
4. Click **Add Event Subscription**
5. Enter your webhook endpoint URL
6. Search "rtms" and select:
- `meeting.rtms_started`
- `meeting.rtms_stopped`
- `webinar.rtms_started` (if using webinars)
- `webinar.rtms_stopped` (if using webinars)
7. Click **Done** then **Save**
### In Zoom Marketplace (Video SDK App)
1. Go to your Video SDK app settings
2. Add Event Subscription:
- `session.rtms_started`
- `session.rtms_stopped`
### Required Scopes
**For Meetings** (Features → Scopes → Add Scopes → search "rtms"):
| Scope | Purpose |
|-------|---------|
| `meeting:read:meeting_audio` | Access meeting audio |
| `meeting:read:meeting_video` | Access meeting video |
| `meeting:read:meeting_transcript` | Access transcripts |
| `meeting:read:meeting_chat` | Access chat messages |
**For Webinars** (add these in addition to meeting scopes):
| Scope | Purpose |
|-------|---------|
| `webinar:read:webinar_audio` | Access webinar audio |
| `webinar:read:webinar_video` | Access webinar video |
| `webinar:read:webinar_transcript` | Access webinar transcripts |
| `webinar:read:webinar_chat` | Access webinar chat messages |
**For Video SDK**: Uses SDK Key/Secret credentials instead of OAuth scopes.
## Products Supporting RTMS
| Product | Start Event | Stop Event | Payload ID | App Type |
|---------|-------------|------------|------------|----------|
| **Zoom Meetings** | `meeting.rtms_started` | `meeting.rtms_stopped` | `meeting_uuid` | General App |
| **Zoom Webinars** | `webinar.rtms_started` | `webinar.rtms_stopped` | `meeting_uuid` (not webinar_uuid!) | General App |
| **Zoom Video SDK** | `session.rtms_started` | `session.rtms_stopped` | `session_id` | Video SDK App |
| Zoom Contact Center | `contactcenter.rtms_*` | `contactcenter.rtms_*` | See Zoom docs | Contact Center App |
| Zoom Phone | `phone.rtms_*` | `phone.rtms_*` | See Zoom docs | General App |
> **Key differences**: Meetings and webinars use a General App with OAuth credentials. Video SDK uses a Video SDK App with SDK Key/Secret. Once connected, the WebSocket protocol is identical across all products.
## Resources
- **Event reference**: https://developers.zoom.us/docs/rtms/event-reference/
- **RTMS docs**: https://developers.zoom.us/docs/rtms/