Zoom Rtms
OpenAIOfficialCreated by ZoomDistributed by OpenAI
- Skill
- Tool integration
- Automation and integrations
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The zoom-rtms skill provides the necessary protocols and architectural guidance for integrating Zoom Realtime Media Streams. It covers the WebSocket-based connection lifecycle, including signaling and media handshakes, heartbeat management, and reconnection strategies. The skill supports processing various media types such as audio, video, screen sharing, transcripts, and chat, while providing reference data for message types and event enums.
What it does
- Establishes WebSocket connections for signaling and media streams.
- Handles media handshake requests for audio, video, screen share, transcript, and chat data.
- Provides reference tables for RTMS message types and event enums.
- Defines mandatory heartbeat and reconnection logic for stream stability.
- Supports configuration for transcript language identification.
- Outlines the process for extracting stream IDs and generating HMAC-SHA256 signatures.
When to use it
- When you need to ingest raw meeting or contact center media into a backend pipeline.
- When building services for transcription, AI analysis, or recording of Zoom sessions.
- When you need to subscribe to specific meeting events or participant media streams.
- When you require programmatic access to media data without a visible participant bot.
Advantages
- Provides a structured approach to managing the complex WebSocket lifecycle.
- Includes comprehensive references for message types and event handling.
- Offers clear guidance on signature generation and security requirements.
- Enables granular control over media subscription and transcript settings.
Usage examples
- “How do I initiate a connection to an RTMS stream?”
- The agent will guide you through receiving the rtms_started webhook, generating an HMAC-SHA256 signature, and performing the signaling and media handshakes.
- “What is the correct way to handle heartbeats in RTMS?”
- The agent will explain that you must respond to msg_type 12 (Keep Alive Request) with msg_type 13 (Keep Alive Response) to maintain the connection.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/openai/zoom-rtms/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip zoom-rtms.zip -d .agents/skills
Documentation by Zoom
Zoom Realtime Media Streams
Use this skill when the integration needs real-time meeting or contact-center media as a backend stream. If the workflow needs a visible participant bot, compare against build-zoom-bot and zoom-meeting-sdk-linux before choosing RTMS.
Workflow
Confirm the source surface: Meetings RTMS, Contact Center voice media, or another documented RTMS stream.
Decide whether RTMS is sufficient or whether a Meeting SDK bot is required for participant identity, meeting controls, or UI-visible behavior.
Implement the WebSocket lifecycle first: event subscription, connection validation, media start, heartbeat, reconnect, and shutdown.
Process media types intentionally: audio, video, screen share, chat, live transcript, and metadata have different payload and timing constraints.
Design downstream pipelines for buffering, transcription, AI analysis, recording, or tool invocation.
Debug by isolating app setup, webhook/event delivery, stream authorization, network connectivity, and media payload decoding.
References
Full preserved guide: references/full-guide.md
Connection architecture: concepts/connection-architecture.md
Lifecycle flow: concepts/lifecycle-flow.md
Media types: references/media-types.md
Data types: references/data-types.md
RTMS bot example: examples/rtms-bot.md
Common issues: troubleshooting/common-issues.md
Files
- ATTRIBUTION.mdAdded by AutoGineer860 B
- LICENSELicence file added by AutoGineer1.1 KiB
- RUNBOOK.md2.6 KiB
- SKILL.md1.6 KiB
- agents/openai.yaml140 B
- concepts/connection-architecture.md9.1 KiB
- concepts/lifecycle-flow.md13.4 KiB
- examples/ai-integration.md11.3 KiB
- examples/manual-websocket.md16.8 KiB
- examples/rtms-bot.md25.9 KiB
- examples/sdk-quickstart.md9.3 KiB
- references/connection.md8.0 KiB
- references/data-types.md13.7 KiB
- references/environment-variables.md1.3 KiB
- references/full-guide.md22.9 KiB
- references/media-types.md6.0 KiB
- references/quickstart.md6.6 KiB
- references/webhooks.md7.4 KiB
- troubleshooting/common-issues.md10.4 KiB
troubleshooting/common-issues.md
Common Issues
Troubleshooting guide for Zoom RTMS.
Quick Diagnostics
| Symptom | Likely Cause | Solution |
|---|---|---|
| Connection fails | Invalid signature | Check signature generation |
| Duplicate connections | Slow webhook response | Respond 200 immediately |
| No data received | Wrong media type | Check media_type bitmask |
| Connection closes | Missing heartbeat | Respond to msg_type 12 |
| Segmentation fault | Old Node.js | Upgrade to 20.3.0+ |
Connection Issues
Webhook Response Timing
Problem: Random disconnections, duplicate connections
Cause: If your webhook handler takes too long to respond, Zoom retries. The retry creates a second connection, which kicks out the first (only 1 connection allowed per stream).
Solution: Respond 200 IMMEDIATELY before any processing:
// CORRECT
app.post('/webhook', (req, res) => {
res.status(200).send(); // FIRST!
// Then process asynchronously
setImmediate(() => {
handleRTMSEvent(req.body);
});
});
// WRONG
app.post('/webhook', async (req, res) => {
await heavyProcessing(req.body); // Zoom retries while waiting!
res.status(200).send();
});Duplicate Connection Prevention
Problem: Multiple connections to same stream
Solution: Track active sessions:
const activeSessions = new Map();
function handleRTMSStarted(payload) {
const streamId = payload.rtms_stream_id;
if (activeSessions.has(streamId)) {
console.log('Already connected, ignoring duplicate');
return;
}
activeSessions.set(streamId, Date.now());
connectToRTMS(payload);
}
function handleRTMSStopped(payload) {
activeSessions.delete(payload.rtms_stream_id);
}Invalid Signature
Problem: Handshake fails with status_code 3
Cause: Signature generation incorrect
Solution: Verify format:
// Message format: "clientId,meetingUuid,streamId"
const message = `${clientId},${meetingUuid},${streamId}`;
const signature = crypto.createHmac('sha256', clientSecret)
.update(message)
.digest('hex');Checklist:
[ ] Using correct clientId (not app name)
[ ] Using correct clientSecret
[ ] No extra spaces in message
[ ] Using hex output (not base64)
Connection Timeout
Problem: WebSocket connection times out
Causes:
Network issues
Firewall blocking WebSocket
Server URL expired
Solution:
Check network connectivity
Ensure firewall allows WSS
Use fresh webhook payload (don't cache URLs)
Heartbeat Issues
Connection Closes Unexpectedly
Problem: Connection closes after ~60 seconds
Cause: Not responding to heartbeat
Solution: Respond to msg_type 12 with msg_type 13:
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.msg_type === 12) {
ws.send(JSON.stringify({
msg_type: 13,
timestamp: msg.timestamp
}));
}
});Timeouts:
Signaling: ~60 seconds
Media: ~65 seconds
Media Data Issues
No Audio Data
Causes:
Wrong media_type in handshake
No participants speaking
Audio not enabled in meeting
Solution:
Verify media_type includes AUDIO (1):
media_type: 1 // Just audio media_type: 9 // Audio + Transcript media_type: 32 // All mediaWait for participant to speak
Check meeting audio settings
No Video Data
Causes:
Wrong media_type
No video enabled
Wrong codec for FPS
Individual video mode enabled but no participant subscription sent
Solution:
Include VIDEO (2) in media_type
Use H.264 for fps > 5:
video: { codec: 7, // H.264 resolution: 2, // HD fps: 25 // > 5 requires H.264 }If using
VIDEO_SINGLE_INDIVIDUAL_STREAM, also:subscribe to
PARTICIPANT_VIDEO_ON/PARTICIPANT_VIDEO_OFFsend
VIDEO_SUBSCRIPTION_REQwith a liveuser_idremember a new request replaces the previous participant stream
No Screen Share Data
Problem: Not receiving screen share even when active
Cause: Screen share is SEPARATE from video
Solution: Include DESKSHARE (4) in media_type:
media_type: 4 // Just screen share
media_type: 5 // Audio + screen share
media_type: 32 // All mediaTranscript Language Delay
Problem: noticeable startup delay before transcription stabilizes
Cause: Language Identification (LID) is enabled and RTMS is auto-detecting / auto-switching languages
Solution: Set a source language and disable LID when you want a fixed language:
transcript: {
content_type: 5,
src_language: 9, // English
enable_lid: false // Fixed language, no auto-switch
}Language IDs:
| ID | Language |
|---|---|
| 9 | English |
| 4 | Chinese (Simplified) |
| 20 | Japanese |
| 21 | Korean |
| 28 | Spanish |
See Data Types for full list.
Participant Video Events Arrive But No Video Stream Follows
Problem: You receive PARTICIPANT_VIDEO_ON, but no actual participant video frames arrive.
Cause: Those events only tell you whose camera is currently available. They do not automatically switch the data socket to that participant.
Solution:
open the video media socket with
VIDEO_SINGLE_INDIVIDUAL_STREAMhandle
PARTICIPANT_VIDEO_ON/PARTICIPANT_VIDEO_OFFchoose one
user_idsend
VIDEO_SUBSCRIPTION_REQwait for
VIDEO_SUBSCRIPTION_RESP
Also remember:
only one participant stream is supported at a time
a newer subscription overrides the previous participant stream
Stream Never Closes Cleanly From Backend
Problem: Your app finishes processing, but the RTMS stream remains open until external stop events arrive.
Solution: Use the new graceful-close control message on the signaling socket:
signalingWs.send(JSON.stringify({
msg_type: 21, // STREAM_CLOSE_REQ
rtms_stream_id: streamId
}));Treat STREAM_CLOSE_RESP as acknowledgement, then continue with local cleanup.
SDK-Specific Issues
Segmentation Fault
Problem: App crashes with segmentation fault
Cause: Node.js version < 20.3.0
Solution:
# Check version
node --version
# Upgrade with nvm
nvm install 24
nvm use 24
# Clear cache and reinstall
npm cache clean --force
rm -rf node_modules package-lock.json
npm installAudio Metadata Missing userId
Problem: onAudioData metadata doesn't include speaker userId
Cause: Using AUDIO_MIXED_STREAM (all audio combined)
Solution: Use onActiveSpeakerEvent for speaker identification:
client.onActiveSpeakerEvent((timestamp, userId, userName) => {
console.log(`Current speaker: ${userName}`);
});Or use AUDIO_MULTI_STREAMS:
client.setAudioParams({
dataOpt: 2 // Per-participant streams
});Video Parameters Ignored
Problem: setVideoParams not taking effect
Cause: SDK bug - video params ignored after audio params
Workaround: Call setVideoParams BEFORE setAudioParams:
// CORRECT ORDER
client.setVideoParams({ codec: 7, fps: 25 });
client.setAudioParams({ codec: 4, sampleRate: 3 });
client.join(payload);SDK Invalid State
Problem: "Invalid status" error on join
Cause: SDK still cleaning up from previous session
Solution: Retry with delay:
try {
client.join(payload);
} catch (error) {
if (error.message?.includes('Invalid status')) {
console.warn('SDK cleaning up, retrying in 2s');
setTimeout(() => {
client.join(payload);
}, 2000);
}
}Platform Issues
Platform Not Supported
Problem: SDK installation fails
Currently Supported:
darwin-arm64 (Apple Silicon)
linux-x64
Not Yet Supported:
Windows
darwin-x64 (Intel Mac)
linux-arm64
Workaround: Use Manual WebSocket implementation.
Status Code Reference
| Code | Name | Description |
|---|---|---|
| 0 | STATUS_OK | Success |
| 3 | STATUS_INVALID_SIGNATURE | Invalid signature |
| 8 | STATUS_DUPLICATE_SIGNAL_REQUEST | Already connected (signaling) |
| 16 | STATUS_DUPLICATE_MEDIA_DATA_CONNECTION | Already connected (media) |
| 40 | STATUS_INVALID_RTMS_SESSION_ID | Invalid RTMS session ID |
| 43 | STATUS_INVALID_MEDIA_TRANSCRIPT_SROUCE_LANGUAGE | Invalid transcript source language |
See Data Types for complete list.
Product-Specific Issues
Video SDK Issues
Problem: Signature validation fails for Video SDK sessions
Cause: Using OAuth Client ID/Secret instead of SDK Key/Secret, or using meeting_uuid instead of session_id.
Solution:
Video SDK apps use SDK Key (as
clientId) and SDK Secret (asclientSecret).Video SDK webhook payloads contain
session_id, NOTmeeting_uuid.The HMAC signature must use
session_id:HMAC-SHA256(sdkSecret, "sdkKey,sessionId,streamId")
// Extract the correct ID based on product
const idValue = payload.meeting_uuid || payload.session_id;
const signature = generateSignature(clientId, idValue, streamId, clientSecret);Webinar Issues
Problem: Looking for webinar_uuid in the payload
Cause: Expecting a webinar-specific UUID field.
Solution: Webinar RTMS payloads still use meeting_uuid (NOT webinar_uuid). This is a common gotcha. The signature, connection flow, and protocol are identical to meetings.
Problem: Missing attendee streams in webinars
Cause: Webinar attendees are view-only participants.
Solution: Only panelist audio/video streams are confirmed to be available via RTMS. Attendee streams may not be available individually. Design your application to work with panelist streams only.
Problem: Practice session not triggering RTMS
Cause: Practice sessions are not documented for RTMS support.
Solution: RTMS events are expected when the webinar goes live to attendees, not during practice sessions. Q&A and Polls data are also not exposed via RTMS.
Getting Help
Developer Forum: https://devforum.zoom.us/
GitHub Issues: https://github.com/zoom/rtms/issues
Official Docs: https://developers.zoom.us/docs/rtms/
Next Steps
Connection Architecture - Understand the protocol
Lifecycle Flow - Correct connection sequence
Data Types - All status codes and enums
Requirements and compatibility
Written for: OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Zoom
- Publisher
- OpenAI (Official)
- Distributed by
- OpenAI
- Source
- openai/plugins · plugins/zoom/skills/rtms · commit d420d13
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from plugins/zoom/LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Zoom or OpenAI.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download zoom-rtms.zip