Generated banner for Zoom Rtms
Generated logo for Zoom Rtms

Zoom Rtms

OpenAIOfficialCreated by ZoomDistributed by OpenAI

  • Skill
  • Tool integration
  • Automation and integrations
  • MIT
  • No issues found

From the author

Use when using RTMS.
— the description by Zoom

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

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/openai/zoom-rtms/download
  2. Unzip 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

  1. Confirm the source surface: Meetings RTMS, Contact Center voice media, or another documented RTMS stream.

  2. Decide whether RTMS is sufficient or whether a Meeting SDK bot is required for participant identity, meeting controls, or UI-visible behavior.

  3. Implement the WebSocket lifecycle first: event subscription, connection validation, media start, heartbeat, reconnect, and shutdown.

  4. Process media types intentionally: audio, video, screen share, chat, live transcript, and metadata have different payload and timing constraints.

  5. Design downstream pipelines for buffering, transcription, AI analysis, recording, or tool invocation.

  6. Debug by isolating app setup, webhook/event delivery, stream authorization, network connectivity, and media payload decoding.

References

Files

troubleshooting/common-issues.md

Common Issues

Troubleshooting guide for Zoom RTMS.

Quick Diagnostics

SymptomLikely CauseSolution
Connection failsInvalid signatureCheck signature generation
Duplicate connectionsSlow webhook responseRespond 200 immediately
No data receivedWrong media typeCheck media_type bitmask
Connection closesMissing heartbeatRespond to msg_type 12
Segmentation faultOld Node.jsUpgrade 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:

  1. Check network connectivity

  2. Ensure firewall allows WSS

  3. 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:

  1. Wrong media_type in handshake

  2. No participants speaking

  3. Audio not enabled in meeting

Solution:

  1. Verify media_type includes AUDIO (1):

    media_type: 1  // Just audio
    media_type: 9  // Audio + Transcript
    media_type: 32 // All media
  2. Wait for participant to speak

  3. Check meeting audio settings

No Video Data

Causes:

  1. Wrong media_type

  2. No video enabled

  3. Wrong codec for FPS

  4. Individual video mode enabled but no participant subscription sent

Solution:

  1. Include VIDEO (2) in media_type

  2. Use H.264 for fps > 5:

    video: {
      codec: 7,        // H.264
      resolution: 2,   // HD
      fps: 25          // > 5 requires H.264
    }
  3. If using VIDEO_SINGLE_INDIVIDUAL_STREAM, also:

    • subscribe to PARTICIPANT_VIDEO_ON / PARTICIPANT_VIDEO_OFF

    • send VIDEO_SUBSCRIPTION_REQ with a live user_id

    • remember 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 media
Transcript 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:

IDLanguage
9English
4Chinese (Simplified)
20Japanese
21Korean
28Spanish

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:

  1. open the video media socket with VIDEO_SINGLE_INDIVIDUAL_STREAM

  2. handle PARTICIPANT_VIDEO_ON / PARTICIPANT_VIDEO_OFF

  3. choose one user_id

  4. send VIDEO_SUBSCRIPTION_REQ

  5. wait 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 install
Audio 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

CodeNameDescription
0STATUS_OKSuccess
3STATUS_INVALID_SIGNATUREInvalid signature
8STATUS_DUPLICATE_SIGNAL_REQUESTAlready connected (signaling)
16STATUS_DUPLICATE_MEDIA_DATA_CONNECTIONAlready connected (media)
40STATUS_INVALID_RTMS_SESSION_IDInvalid RTMS session ID
43STATUS_INVALID_MEDIA_TRANSCRIPT_SROUCE_LANGUAGEInvalid 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 (as clientSecret).

  • Video SDK webhook payloads contain session_id, NOT meeting_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

  1. Developer Forum: https://devforum.zoom.us/

  2. GitHub Issues: https://github.com/zoom/rtms/issues

  3. Official Docs: https://developers.zoom.us/docs/rtms/

Next Steps

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