WebRTC Combine
Go backend for temporary multi-participant WebRTC calls backed by a LiveKit SFU.
Architecture
- The Go service owns the public API, room ids, and short-lived LiveKit access tokens.
- LiveKit is the SFU. It handles WebRTC transport, track forwarding, subscriptions, screen sharing, and data messages.
- No registration is required.
- The service stores temporary room metadata in Redis and removes rooms only after their minimum lifetime has elapsed and LiveKit reports no connected participants.
- Chat uses LiveKit data messages. Messages are not persisted; uploaded chat files are retained temporarily by the API.
- Recording is not configured. LiveKit Egress can be added later when recording is needed.
Local Run
Start the Go API and LiveKit SFU in one Docker network:
docker-compose up --build
The Go container talks to the SFU by service name: http://livekit:7880.
For local development without Docker, start LiveKit first and then run:
go run ./cmd/server
Defaults are configured for LiveKit dev mode:
LIVEKIT_API_URL=http://localhost:7880LIVEKIT_PUBLIC_URL=ws://localhost:7880LIVEKIT_API_KEY=devkeyLIVEKIT_API_SECRET=secret
Other environment variables:
ADDR: HTTP listen address, default:8080.API_PUBLIC_URL: base URL used in generated API join links, defaulthttp://localhost:8080.API_BASE_PATH: path prefix handled by the API itself, for example/api; empty by default.DOCS_DIR: OpenAPI directory, defaultdocs.LIVEKIT_API_URL: backend URL for LiveKit server API, for examplehttp://livekit:7880in Docker.LIVEKIT_PUBLIC_URL: URL returned to clients for LiveKit SDK connection, for examplews://localhost:7880.MAX_PARTICIPANTS_PER_ROOM: LiveKit room participant limit, default20.ROOM_TTL: minimum room lifetime and occupied-room extension, default2h.ROOM_CLEANUP_INTERVAL: how often the background cleanup worker checks due rooms, default1m.ROOM_CLEANUP_BATCH_SIZE: maximum rooms inspected during one cleanup pass, default100.JOIN_TOKEN_TTL: validity period of a generated LiveKit join token, default15m.FILE_TTL: how long uploaded chat files remain downloadable, default2h.MAX_FILE_SIZE_MB: maximum uploaded file size in MiB, default25.FILE_STORAGE_TYPE:localors3; defaults tolocal.FILE_STORAGE_DIR: local backend directory, default is the operating system temporary directory.S3_ENDPOINT: S3-compatible endpoint without a URL scheme, for exampleminio:9000.S3_ACCESS_KEY/S3_SECRET_KEY: S3 credentials; keep the secret key in a Secret.S3_BUCKET: file bucket, defaultmeet-chat-files; it is created automatically when missing.S3_REGION: S3 region, defaultus-east-1.S3_USE_SSL: use TLS for the backend S3 connection, defaultfalse.S3_UPLOAD_TEMP_DIR: temporary staging directory used while enforcing the upload limit.ALLOWED_ORIGINS: comma-separated browser origins allowed by CORS.REDIS_ADDR: Redis address, required in production, for examplemeet-redis:6379.REDIS_USERNAME: optional Redis ACL username.REDIS_PASSWORD: optional Redis password; keep it in a Secret.REDIS_DB: Redis logical database, default0.REDIS_KEY_PREFIX: key prefix, defaultwebrtc-combine.
Local development falls back to an in-memory room store when REDIS_ADDR is empty. Production refuses to start without Redis.
Use separate Redis instances for test and production. Room records are stored as:
<REDIS_KEY_PREFIX>:room:<public-room-id>
Room records do not use Redis key TTL. A sorted-set cleanup queue schedules the first check after ROOM_TTL.
The worker deletes a due room only when LiveKit reports no connected participants. Occupied rooms are
rescheduled for another ROOM_TTL, and join requests also extend the room lifetime.
Production
Create the secret environment file and replace the LiveKit credentials with an API key pair configured on the production LiveKit server:
cp .env.production.example .env.production
chmod 600 .env.production
Add the same dedicated API key pair from .env.production to the production LiveKit configuration:
keys:
"<LIVEKIT_API_KEY>": "<LIVEKIT_API_SECRET>"
Restart LiveKit after changing its key configuration. Do not commit or send .env.production; it is ignored by Git.
Deploy the API behind the existing Traefik instance:
docker-compose --env-file .env.production -f docker-compose.production.yml up -d --build
The production route is handled natively by the application:
https://meet.apps.4vkamnyam.ru/api/* → Ingress → API:8080/api/*
Ingress must not rewrite or strip /api. Kubernetes probes can use /health without the prefix.
The API calls LiveKit through https://livekit.4vkamnyam.ru; clients receive wss://livekit.4vkamnyam.ru.
HTTP API
Create a room:
curl -X POST http://localhost:8080/rooms
Join a room by nickname and receive LiveKit connection parameters:
curl -X POST http://localhost:8080/rooms/{roomId}/join \
-H 'Content-Type: application/json' \
-d '{"name":"alex"}'
Get room state:
curl http://localhost:8080/rooms/{roomId}
Upload a file for the room chat:
curl -X POST http://localhost:8080/rooms/{roomId}/files \
-F 'file=@./document.pdf'
The response contains chatMessage. Publish that object as JSON through a reliable LiveKit data message.
Recipients can render it as a file attachment and use chatMessage.file.url to download the file.
Open Swagger UI:
http://localhost:8080/swagger
Client Notes
The frontend should connect to LiveKit with the returned livekitUrl and token.
The token grants:
- room join for the generated LiveKit room;
- publish camera and microphone tracks;
- publish screen-share and screen-share-audio tracks;
- subscribe to other participants;
- publish data messages for in-session chat.
Uploaded files are not sent through the LiveKit data channel. The API stores up to 25 MiB per file and the chat message contains only file metadata and a download URL. Local storage is suitable for development and a single API replica. The S3 backend supports multiple API replicas and works with MinIO or another S3-compatible object store.
The shareable link should contain the public roomId. The client can ask for a nickname, call POST /rooms/{roomId}/join, then connect to LiveKit.