Skip to content

Getting Started

By the end of this page a gpt-realtime agent answers a Microsoft Teams call. You need Node.js >= 20, an OpenAI API key with Realtime access, and a StandIn identity (the sandbox is enough).

Unlike agent platforms with a dashboard, there is no agent to set up on the OpenAI side: the bridge configures each Realtime session itself (model, voice, instructions, VAD, tools) from your environment variables.

As a CLI:

Terminal window
OPENAI_API_KEY=sk-... \
WORKER_SHARED_SECRET=... \
npx @komaa/openai-msteams-bridge

Or embedded in your own project:

Terminal window
npm install @komaa/openai-msteams-bridge
import { loadConfig, startServer } from "@komaa/openai-msteams-bridge";
startServer(loadConfig()); // same env variables as the CLI

Every option is an environment variable; the package ships a fully commented .env.example, and the Configuration Reference documents each one. The bridge listens on 0.0.0.0:8080 by default and exposes GET /healthz for liveness checks.

WORKER_SHARED_SECRET comes from StandIn in the next step.

Section titled “2. Shape the agent (optional, recommended)”

Three variables define the agent’s personality; sensible defaults apply if you skip this:

Terminal window
OPENAI_VOICE=marin
OPENAI_INSTRUCTIONS="You are Komaa's friendly receptionist. Keep replies short; you are speaking aloud on a phone call."
OPENAI_FIRST_MESSAGE="Hello! You've reached Komaa. How can I help?"

The bridge appends per-call caller context (name, tenant, direction) to your instructions automatically, and registers the built-in tools (end_call, look, show_image, express) on every session.

StandIn is the hosted service that joins the Teams call and dials into your bridge. Pick a tier at standin.komaa.com (sandbox for an instant trial), pair, and you get a shared secret.

  1. Put the secret in WORKER_SHARED_SECRET (both sides must match exactly).
  2. Point the identity’s agent WebSocket URL at your bridge, for example wss://oai-bridge.example.com:8080/voice/msteams/stream. StandIn appends /{callId} per call.
  3. Restart the bridge if you changed the env.

StandIn dials in from the internet, so a laptop or private host needs a public URL. A tunnel gives you one and terminates TLS (so you get wss:// for free). Run one pointing at port 8080, then use the wss://…/voice/msteams/stream form of the printed host:

Tailscale Funnel:

Terminal window
tailscale funnel --bg --https=8080 8080

Cloudflare Tunnel:

Terminal window
cloudflared tunnel --url http://localhost:8080

ngrok:

Terminal window
ngrok http 8080

VS Code dev tunnels:

Terminal window
devtunnel host -p 8080 --allow-anonymous

For a fixed production host use an ingress/load balancer, or serve TLS natively with TLS_CERT_PATH + TLS_KEY_PATH. Never give StandIn a plain ws:// URL outside local testing.

More detail (tiers, what pairing does, cutoff behavior): Connecting to StandIn.

Call your Teams bot (or join the sandbox meeting). In the bridge logs you should see the call arrive, the Realtime session open, and the relay start:

INFO [server] worker connected for call 19:meeting_ab... (1/64)
INFO [call:19:meeting_ab] session.start (direction=inbound, recording=unknown)
INFO [call:19:meeting_ab] OpenAI Realtime session open; relaying

Speak, and the agent answers in its own voice. If the call connects but something is off, Troubleshooting maps every error you are likely to see (401 handshake, agent-unavailable, the 60-minute session ceiling) to its cause.