RTMS signaling socket handshake

I’m integrating RTMS real-time meeting transcripts into a Spring Boot (Java) application. I’m attempting to connect to the RTMS signaling WebSocket, but the connection closes immediately after I send the handshake request.

It looks like the issue may be related to my signature generation or the handshake payload, and I’d appreciate help reviewing them.

Problem symptoms:

  • I open the RTMS signaling WebSocket.

  • I generate the signature and send the handshake message.

  • As soon as the handshake is sent, the WebSocket connection gets closed by the server.

  • No additional error message is returned by Zoom, just status_code = 1000 and reason - Normal close.

What I need help with:

  • Verifying whether my signature generation is correct.

  • Confirming if the handshake message structure matches Zoom’s RTMS documentation.

  • Do I need some other configurations for the same.

Used signature generation method

public String generateZoomSignature(String meetingUuid, String streamId) {
    try {
        String message = clientId + "," + meetingUuid + "," + streamId;
        Mac sha256 = Mac.getInstance("HmacSHA256");
        sha256.init(new SecretKeySpec(clientSecret.getBytes(), "HmacSHA256"));
        return bytesToHex(sha256.doFinal(message.getBytes()));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

logs -

Webhook event: meeting.rtms_started
Webhook payload: {meeting_uuid=W3XXXXXX, operator_id=y8XXXXXXXXXX, rtms_stream_id=eXXXXXXXXXX, server_urls=wss://zoomiad2XXXXXXXXXXX}
Connecting to signaling WebSocket: wss://zoomiadXXXXXXXXXX
Signaling socket opened
[cTaskExecutor-1] : Sent message: {meeting_uuid=WXXX, rtms_stream_id=eXXXX, protocol_version=1, signature=1XXXXXXXXXXXXX, msg_type=1, sequence=0}
Signaling socket closed: CloseStatus[code=1000, reason=Normal close]

Webhook event: meeting.rtms_stopped
Webhook payload: {meeting_uuid=4XXXXXXXX, rtms_stream_id=7XXXX, stop_reason=11}

There’s a few things that could be wrong here, but at the very least, your signature generation method looks correct as it matches Zoom’s docs to generate a signature for RTMS

For the handshake method, Zoom expects the following handshake message:

const handshakeMsg = {
  msg_type: 1, // SIGNALING_HAND_SHAKE_REQ
  meeting_uuid: meetingUuid,
  rtms_stream_id: rtmsStreamId,
  signature
};

You should also respond to Zoom’s keep-alive messages to ensure Zoom doesn’t close the connection:

if (msg.msg_type === 12) { // KEEP_ALIVE_REQ
  console.log('Received KEEP_ALIVE_REQ, responding with KEEP_ALIVE_RESP');
  signalingWs.send(JSON.stringify({
    msg_type: 13, // KEEP_ALIVE_RESP
    timestamp: msg.timestamp
  }));
}

If these don’t seem like an issue, you should ensure that you meet the requirements to use Zoom RTMS:

  1. To enable RTMS, reach out to your account team. If you don’t have an existing account team contact, fill out this form to reach out to our team and get started with RTMS.
  2. To add RTMS to your app, you need to add the corresponding RTMS scopes to your app. For more information, see Add RTMS features to your app

I’ve already verified all the steps on my end, but the RTMS WebSocket connection closes immediately with the following message:

Signaling socket closed: CloseStatus[code=1000, reason=Normal close]

I’m not getting any detailed explanation for the closure.
Here’s the handshake request I’m sending:

{
  "rtms_stream_id": "bXXXXXXXXXXXXXXX",
  "signature": "aXXXXXXXXXXXXXXXX",
  "msg_type": 1,
  "meeting_uuid": "YXXXXXXXXXXXXXXX"
}

I’m generating the signature exactly as documented and using the same request structure described in the Zoom RTMS documentation. Still, the connection closes right away after opening.

Shortly after, I receive this webhook:

Webhook event: meeting.rtms_stopped
Payload:

{
  "meeting_uuid": "4XXXXXXXX",
  "rtms_stream_id": "7XXXX",
  "stop_reason": 11
}

What could be causing this abrupt connection closure with only a generic “Normal close” (code 1000) and no further explanation? Has anyone encountered this behavior or knows what stop_reason = 11 indicates?

Any insights would be appreciated!

@Vatsal From the error code, it is highly likely there might be a mismatch in the signature.

Here’s a sample of what I’ve generated. It would be helpful to use these test values to test public String generateZoomSignature(String meetingUuid, String streamId)

Assume these values to test your method

ZOOM_CLIENT_ID = “qwerty”
ZOOM_CLIENT_SECRET = "asdfg”
meeting_uuid = “rGK2A1lRTb28MCy8AsD8Bw==”
rtms_stream_id = “7715d705da9f461f861193c16f78bbee”,

Your signature should be

“814d75d0f67faf95ffcd2361019a0afd6e94753fa76f362a48e948550131e7cf”

@chunsiong.zoom @amanda-recallai I ran into an issue where the signature generation was correct, but the documentation only provides Python and Node examples. In those examples, the media socket message comes but no type specified. And previously I was expecting it as text in my Java implementation, but it arrives in binary form - something the docs don’t cover.

I also have a question about RTMS:
Is it possible to use RTMS with a Server-to-Server OAuth app? When I tried, I wasn’t able to add RTMS scopes to a server-to-server OAuth app, but it did work with a general app.

@Vatsal this new sample should help

the specific file of interest is there