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

10 KiB

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:

  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:

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:

  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

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 (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