Live classes and recording
Live classes and mentoring sessions use LiveKit. Three pieces run in the cluster:
- LiveKit server (
livekit-server, imagelivekit-server:v1.9.1when observed). It hosts the rooms. Browsers reach it atlivekit.rtih.peoplewave.inover WSS. Media uses its own load balancer for TCP and UDP. - LiveKit egress (
livekit-egress, a custom image namedlms-egress). It joins a room, composes the video and writes an MP4 to the bucket. - LMS service. It creates classes, mints join tokens, starts and stops egress, and receives the webhooks.
Automatic recording
A class is recorded for the whole time it is in session. Nobody presses record. The recorder starts when the first track is published, not when the class starts, because an egress started in an empty room never signals that it has started and aborts with code 412.
- A mentor starts the class. The status becomes
IN_PROGRESSand the recording status staysNONE. - A participant publishes a track. The LiveKit server sends a
track_publishedwebhook to the LMS service. - The LMS service verifies the webhook, finds the class from the room name (
class-<classId>), and starts a room-composite egress. The recording status becomesRECORDING. This step is idempotent: a class that already has an egress id does not start a second one. - When the class ends, the LMS service stops the egress. The recording status becomes
PROCESSING. - The egress writes
recordings/<classId>/<uuid>.mp4to the bucket, and the server sends anegress_endedwebhook with statusEGRESS_COMPLETE. - The LMS service creates an
LmsRecordingrow and sets the class recording status toREADY.
The live-class recording flow shows this as a sequence diagram.
The webhook
POST /api/v1/lms/live-classes/egress-webhook is a public path. LiveKit signs each webhook with a JWT in the Authorization header, with no Bearer prefix. The JWT is signed with the LiveKit API secret and carries a SHA-256 checksum of the body. LiveKitWebhookVerifier checks the signature and the checksum in constant time. LiveKit sends the body as application/webhook+json, so the endpoint accepts any content type and lets the signature decide.
The server delivers webhooks to http://lms-service:8080/... inside the cluster.
Sources: services/lms-service/src/main/java/com/rtih/startupos/lms/{service/LiveClassService,service/LiveKitWebhookVerifier,resource/EgressWebhookResource}.java, .agents/tasks/lms-egress-412/fix-report.md, infra/k8s/livekit-egress.yaml, infra/oci-devops/deploy/livekit-deployment.yaml, live Deployment images read on 2026-11-07.