7.4 KiB
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).
// 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:
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.
{
"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.
{
"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.
{
"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, NOTwebinar_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.
{
"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.
{
"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_idinstead ofmeeting_uuid. The HMAC signature must usesession_idin place ofmeeting_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.
{
"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 |
// 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)
- Go to your app settings
- Navigate to Features → Access
- Enable Event Subscription
- Click Add Event Subscription
- Enter your webhook endpoint URL
- Search "rtms" and select:
meeting.rtms_startedmeeting.rtms_stoppedwebinar.rtms_started(if using webinars)webinar.rtms_stopped(if using webinars)
- Click Done then Save
In Zoom Marketplace (Video SDK App)
- Go to your Video SDK app settings
- Add Event Subscription:
session.rtms_startedsession.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/