This Spring Boot example demonstrates how to receive real-time audio, video, screen share, transcript, and chat data from a Zoom meeting using the RTMS (Real-Time Media Streaming) service. The Spring server connects to Zoom's RTMS infrastructure via WebSocket, handles webhook events, and processes incoming media messages.
- Java 17 or higher
- Maven 3.6 or higher
- A Zoom account with RTMS enabled
- Zoom App credentials (Client ID and Client Secret)
- Zoom Secret Token for webhook validation
- Install dependencies and build:
mvn clean install- Configure environment variables by creating a
.envfile in the project root:
# Zoom Configuration
ZOOM_CLIENT_ID=your_client_id
ZOOM_CLIENT_SECRET=your_client_secret
ZOOM_SECRET_TOKEN=your_secret_token
# Server Configuration
SERVER_PORT=5050
# Application Configuration
APP_WEBHOOK_PATH=/webhook
APP_MODE=webhook
MEDIA_SOCKET_CONNECTION_MODE=split
MEDIA_TYPES_FLAG=11MEDIA_TYPES_FLAG combines audio (1), video (2), screen share (4),
transcript (8), and chat (16). Split mode expands 11 into separate audio,
video, and transcript sockets whose handshakes use 1, 2, and 8.
Use one unified media socket with:
MEDIA_SOCKET_CONNECTION_MODE=unified
MEDIA_TYPES_FLAG=32Unified mode requires 32; combined masks such as 11 must use split mode.
- Run the application:
mvn spring-boot:run
# or
java -jar target/vsdk-working-java-1.0.0.jar- Expose your local server using a tool like ngrok:
ngrok http 5050- Set your Zoom App's Event Notification URL to point to your ngrok endpoint, e.g.:
https://<your-ngrok-subdomain>.ngrok.io/webhook
- Start a Zoom meeting and initiate RTMS streaming.
src/main/java/com/zoom/rtms/workingjava/
├── WorkingJavaApplication.java # Main Spring Boot application
├── config/
│ ├── ZoomConfig.java # Zoom configuration properties
│ └── AppConfig.java # Application configuration properties
├── controller/
│ └── WebhookController.java # REST endpoint for webhook handling
├── model/
│ ├── WebhookEvent.java # Webhook payload model
│ ├── RtmsStatus.java # RTMS status codes enum
│ ├── RtmsStates.java # RTMS states and enums
│ └── RtmsMessages.java # RTMS protocol message DTOs
└── service/
├── RtmsService.java # Core RTMS service with WebSocket clients
└── RtmsConnection.java # Connection state management
Zoom App/Webhook ──► WebhookController ──► RtmsService
│ │ │
└── Validation Response ◄─┘ │
│
▼
RTMS WebSocket Flow:
1. Signaling Handshake
2. Media URL Response
3. Media Handshake
4. Event Subscription
5. Media Data Streaming
6. Keep-alive messages
The application maintains one signaling connection plus either one unified media connection or one media connection for each selected type.
- Handles session management and control messages
- Exchanges handshake, keep-alive, and event messages
- Provides media server URLs
- Receives actual media data (audio, video, transcript, chat)
- Handles binary data streams
- Processes keep-alive messages
The application implements the Zoom RTMS WebSocket protocol:
1: SIGNALING_HANDSHAKE_REQ2: SIGNALING_HANDSHAKE_RESP5: EVENT_SUBSCRIPTION_REQ6: EVENT_MESSAGE7: READY_NOTIFICATION (sent after media handshake succeeds)8: STREAM_STATE_CHANGE9: SESSION_STATE_CHANGE12: KEEP_ALIVE_REQ13: KEEP_ALIVE_RESP
3: DATA_HANDSHAKE_REQ4: DATA_HANDSHAKE_RESP12-13: Keep-alive messages14: Audio data15: Video data16: Screen share data17: Transcript data18: Chat data
ZOOM_CLIENT_ID: Your Zoom app client IDZOOM_CLIENT_SECRET: Your Zoom app client secretZOOM_SECRET_TOKEN: Secret token for webhook validationSERVER_PORT: Server port (default: 5050)APP_WEBHOOK_PATH: Webhook endpoint path (default: /webhook)APP_MODE: Application mode (default: webhook)MEDIA_SOCKET_CONNECTION_MODE:split(default) orunifiedMEDIA_TYPES_FLAG: Selected media bitmask; unified mode requires32
The application automatically strips surrounding quotes from configuration values, so ZOOM_CLIENT_ID="value" and ZOOM_CLIENT_ID=value are both handled correctly.
- HMAC-SHA256 signatures for WebSocket authentication
- Webhook URL validation with encrypted tokens
- Connection state management and cleanup
- Keep environment variables secure and not committed to version control
The application provides detailed logging for:
- WebSocket connection states
- RTMS session and stream states
- Media data reception
- Error conditions and troubleshooting
Set different log levels as needed:
logging.level.com.zoom=DEBUG
logging.level.org.springframework.web.socket=WARNmvn clean compilemvn testmvn spring-boot:run -Dspring-boot.run.profiles=dev- This example focuses on RTMS event processing and data reception
- WebSocket connections automatically handle handshakes and keep-alive messages
- Media data is currently logged but can be extended to process/save binary data
- Ensure your Zoom App has RTMS enabled and proper webhook permissions
- RTMS requires Zoom account-level configuration
This Spring Boot implementation provides:
- Type safety with Java records and enums
- Spring Boot ecosystem (auto-configuration, dependency injection)
- Structured logging with SLF4J
- WebSocket client implementation with Java native HttpClient
- Spring MVC for REST endpoints
- Async processing with @Async annotations
Equivalent features from the Node.js version:
- Webhook handling with validation
- RTMS session start/stop
- Split or unified media WebSocket connections
- Event processing and logging
- Connection reconnection logic
- HMAC signature generation
The project runs the Spring Boot meeting RTMS webhook and media receiver. Its multi-stage Dockerfile keeps build tooling out of the final runtime image and does not hard-code a CPU architecture.
Build and run it from the rtms-samples repository root:
docker build -f boilerplate/working_java/Dockerfile -t rtms-boilerplate-working_java .
docker run --rm --env-file boilerplate/working_java/.env -p 3000:3000 rtms-boilerplate-working_javaRun the build from the repository root because the Dockerfile uses repository-relative paths. Runtime secrets are supplied with --env-file and are excluded from the image build context.
Normal Zoom webhook deliveries are verified against the exact raw request body using
x-zm-signature and x-zm-request-timestamp. Configure ZOOM_SECRET_TOKEN with the
Marketplace app's webhook Secret Token. Requests with missing, invalid, or stale
signatures are rejected; the default replay window is 300 seconds and can be changed
with WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS.