Skip to main content
The bridge is configured entirely by environment variables (a .env file works with node --env-file=.env). Numeric variables fail loudly at startup if set to a non-number or a negative value, so a typo stops the process with a clear message rather than silently misbehaving.

Required

VariableMeaning
WORKER_SHARED_SECRETMust equal the shared secret from StandIn pairing. Both sides sign the WebSocket upgrade with it; a mismatch is rejected with 401.
LIVEKIT_URLLiveKit server URL - a Cloud project (wss://<project>.livekit.cloud) or self-hosted.
LIVEKIT_API_KEYLiveKit API key. Mints join tokens, dispatches agents, deletes rooms. Server-side only.
LIVEKIT_API_SECRETLiveKit API secret paired with the key.

Agent dispatch

VariableDefaultMeaning
LIVEKIT_AGENT_NAME(unset)The agentName your worker registers with, for explicit dispatch (recommended). Unset falls back to automatic dispatch (an unnamed agent joins every room; prototype-only).
LIVEKIT_ROOM_PREFIXmsteams-Room name prefix; the room is ${prefix}${callId} (sanitized).
LIVEKIT_DELETE_ROOM_ON_ENDtrueDelete the room at teardown so the agent job ends immediately instead of idling out (billing hygiene).
The dispatch carries per-call metadata to your agent (ctx.job.metadata, JSON): source, caller_name, tenant_id, call_direction, and user_id (AAD id, only when Teams provides one). Your agent can also listen on two data topics: teams.context (participant count/DTMF) and teams.goodbye (the governor’s goodbye line to speak). See Agents and dispatch.

Governor

VariableDefaultMeaning
MAX_CALL_MINUTES0 (off)Bridge-side hard cap per call. LiveKit does not know your billing, so enforce limits here. On expiry the bridge asks the agent to say goodbye, waits the grace, then ends the call.
GOODBYE_TEXTa default lineThe goodbye line sent on the teams.goodbye data topic.
GOODBYE_GRACE_MS8000How long to let the goodbye play before ending the call.
There is no bridge-side TTS on the room transport - the goodbye is a teams.goodbye data message your agent speaks. Have your handler interrupt the current turn so the goodbye actually plays; if the current turn outlasts GOODBYE_GRACE_MS it can be cut.

Transport hardening

VariableDefaultMeaning
HMAC_FRESHNESS_MS60000Allowed clock skew for the upgrade timestamp and the replay-guard window.
MAX_CONNECTIONS64Max concurrent worker connections.
MAX_CONNECTIONS_PER_IP= MAX_CONNECTIONSPer-IP cap. Default is the global cap (StandIn dials from a small fixed egress set); set it if the bridge is more broadly exposed.
PRE_START_TIMEOUT_MS10000Drop a client that authenticates but never sends session.start.
WORKER_IDLE_TIMEOUT_MS90000Dead-peer window: end the call after this long with no worker message (the worker heartbeats every 30 s).
TRUST_PROXY_XFFfalseTrust the first X-Forwarded-For hop for the per-IP cap. Enable only behind a single proxy that overwrites the header.
PORT / BIND / LOG_LEVEL8080 / 0.0.0.0 / infoListen port, bind address, log verbosity.

TLS

There is no TLS variable: the bridge serves plain WebSocket by design. Front it with a TLS terminator (tunnel, ingress, or load balancer) - StandIn requires wss:// in any real deployment.

Privacy

The bridge stores nothing - audio is relayed frame-by-frame between the worker socket and the LiveKit room and never written to disk; recording.status is logged only, and /metrics exposes counters, never call content. Whatever your agent and LiveKit retain follows their own settings. The room is deleted at teardown (LIVEKIT_DELETE_ROOM_ON_END=true). See Governors and privacy.

Docs site

Installation

Source