Skip to main content
The fastest way to understand the bridge is to run its example project - a minimal, working embedding you can copy straight into your own repo. Budget about 10 minutes once the Teams setup is done.

examples/basic-bridge

loadConfig() + startServer() in a few lines, with a custom vision hook (your own model answers the agent’s look tool - the raw frame never leaves your process). Env-file config and graceful shutdown are built in.

What you need first

  • Node.js >= 20.
  • An ElevenLabs agent (Agents dashboard) with audio input and output format set to PCM 16000 Hz, plus an API key.
  • A StandIn identity with its shared secret (from pairing or the dashboard). The sandbox works too if you have no Teams bot yet.

Create the ElevenLabs agent

The bridge relays audio verbatim at 16 kHz, so the one thing that must be right on the ElevenLabs side is the agent’s audio format. Create an agent in the Agents dashboard, then set:
SettingValueWhy it matters
Output audio formatPCM 16000 HzThe agent’s voice reaches Teams as-is; any other rate is garbled / wrong-pitch. This is the one people miss.
Input (ASR) audio formatPCM 16000 HzCaller audio arrives at 16 kHz.
Modeleleven_turbo_v2 or eleven_flash_v2An English agent rejects eleven_turbo_v2_5 - use turbo or flash v2.
Languageyour call languageSet it explicitly (e.g. English) so the model choice above is validated.
The dashboard has more than one “output format” control - the widget / embed format is not the same as the conversation format the bridge actually uses (tts.agent_output_audio_format). If a call is garbled even though the UI looks like 16 kHz, the conversation format is still wrong. Set it explicitly and re-check with the snippet below.
Then grab two values for your .env in the next steps:
  • Agent ID (agent_...) from the agent’s page, for ELEVENLABS_AGENT_ID.
  • API key from dashboard -> API keys, for ELEVENLABS_API_KEY.
Check the format the bridge will actually get, regardless of what the UI shows:
curl -s -H "xi-api-key: $ELEVENLABS_API_KEY" \
  https://api.elevenlabs.io/v1/convai/agents/$ELEVENLABS_AGENT_ID \
  | grep -o '"agent_output_audio_format":"[^"]*"'
If that is not pcm_16000, set it directly:
curl -s -X PATCH -H "xi-api-key: $ELEVENLABS_API_KEY" -H "content-type: application/json" \
  -d '{"conversation_config":{"tts":{"agent_output_audio_format":"pcm_16000"}}}' \
  https://api.elevenlabs.io/v1/convai/agents/$ELEVENLABS_AGENT_ID

1. Clone and install

git clone https://github.com/komaa-com/elevenlabs-msteams-bridge
cd elevenlabs-msteams-bridge/examples/basic-bridge
npm install

2. Configure the environment

cp .env.example .env
Open .env and fill in the three required values:
VariableWhat to put there
ELEVENLABS_API_KEYYour ElevenLabs API key (server-side only; never sent to the Teams side).
ELEVENLABS_AGENT_IDThe agent that should answer calls, from the Agents dashboard.
WORKER_SHARED_SECRETThe shared secret from StandIn pairing - both sides must match exactly.
The example still boots with dummy values, so you can check the wiring before the credentials are real. Everything else (PORT, MAX_CALL_MINUTES, goodbye behavior, regional hosts) has sensible defaults - see the configuration reference.

3. Start it

npm start
The bridge prints the WebSocket URL to give StandIn:
Point your StandIn identity's agent WebSocket URL at ws://<this-host>:8080/voice/msteams/stream

4. Expose port 8080

StandIn connects from the internet, so the port needs a public wss:// URL. Any tunnel works:
tailscale funnel --bg --https=8080 8080
Your URL: wss://<machine>.<tailnet>.ts.net:8080/voice/msteams/stream

5. Connect it to StandIn and call

  1. In your StandIn dashboard, set the identity’s Agent voice URL to the wss:// URL from step 4.
  2. Make sure the identity’s shared secret equals WORKER_SHARED_SECRET.
  3. Place a Teams call to your bot (or join the sandbox meeting). StandIn joins, connects to the bridge, and your ElevenLabs agent answers.

The vision hook

The example ships a custom VisionDescriber stub in index.mjs. When your agent calls its look client tool, the bridge hands your function the current camera or screen-share frame and returns your text description to the agent - the raw frame never leaves your process. Replace the stub with a call to any vision-capable model. If you prefer configuration over code, the VISION_API_URL / VISION_API_KEY / VISION_MODEL variables do the same against any OpenAI-compatible endpoint - see Vision.

From example to your own project

Depend on the published package instead of the local checkout:
npm install @komaa/elevenlabs-msteams-bridge
Then copy the example’s index.mjs as your starting point. The library API documents the programmatic surface (config, server handle, hooks).

If something does not work

  • WebSocket rejected with 401 - WORKER_SHARED_SECRET does not match the secret in StandIn.
  • Bot joins the call but stays silent - the Agent voice URL is unreachable from the internet or points at the wrong port; re-check the tunnel.
  • Agent audio garbled or missing - the ElevenLabs agent’s audio input/output format is not PCM 16000 Hz.
  • More: Troubleshooting.

Example README

Configuration

Library API