Build a click-to-talk button that connects a browser user to a server-side AI voice agent. The SDK captures the user’s microphone, encodes it as Opus, and publishes it as the call’s uplink. The agent’s synthesized speech arrives as the downlink and plays back through WebCodecs. Media travels as MoQT over QUIC / WebTransport end-to-end. The code uses the browser’s WebRTC stack only as a codec + capture tap, never as the transport. There is no SFU in the path. Three helpers make this a few lines of code instead of a WebCodecs project:
  • captureMicrophone — runs the browser’s AEC / AGC / noise-suppression and Opus encoder in a loopback RTCPeerConnection. It then diverts the encoded frames onto your uplink track. Raw PCM never crosses the wire.
  • OpusPlayer — decodes received Opus with WebCodecs and renders it through an AudioWorklet ring buffer.
  • attachCaller — the browser-caller side of the audio bridge. It subscribes to the downlink (voice/<sid>/downlink, the audio that the cloud sends the caller). It publishes the uplink (voice/<sid>/uplink, the caller’s mic).
The browser media plane is WebTransport-only today. There is no WebSocket fallback rung in the browser SDK yet. The QUIC→WebSocket ladder ships only in the native cores. captureMicrophone also hard-refuses to run if RTCRtpScriptTransform is missing. It does not fall back to an insecure main-thread path. In practice you need a current Chromium (also required for the WebCodecs AudioDecoder playback), Edge, Firefox 133+, or Safari TP. See Browser compatibility.

How the pieces connect

1

Mint the call server-side

Your backend originates the call with an agent attached. It hands the browser back the sid plus a browser-scoped token. Your secret API key never ships to the client.
2

Start playback

Create and start() an OpusPlayer. This must happen inside a user gesture so that the AudioContext can resume.
3

Attach as the caller

attachCaller(sid, { codec: "opus", onDownlink }) subscribes to the agent’s audio and pushes each frame into the player.
4

Capture the mic

captureMicrophone(...) forwards encoded Opus frames onto the uplink. The agent then hears the user.

Front end: the click-to-talk component

This is the whole browser side. It assumes a page with a dial button. It holds a browser-scoped key: a publishable, voice-scoped, short-TTL token, not your server secret (see the backend below). The browser mic capture is inherently a Web API, so this half is TypeScript only. The backend shows both TypeScript and Python.
captureMicrophone runs the browser’s echo-cancellation, gain control, and noise suppression. It then diverts the encoded Opus frames onto the uplink. Raw PCM never leaves the tab. The engine has no server-side AEC/AGC. Those constraints must do their job in the browser, or the agent hears its own voice. Details are in WebRTC diversion and RTCRtpScriptTransform.

Minimal backend

The browser must not hold your secret API key. A small server route mints the call and attaches the AI agent that answers. The route returns the sid plus a publishable token scoped to this one call. The route creates the call over a voice-routing trunk that you provision for web sessions. to / from label the leg. For a pure-web agent, they do not need to be dialable numbers.
agent binds a server-side agent (ASR→LLM→TTS or a speech-to-speech provider). The agent answers the call and drives both legs. You do not open an AudioBridge on the server. To run your own dialog logic instead, drop agent and bridge the audio yourself. See Bring your own ASR / LLM / TTS.

Local development

WebTransport with a browser needs a secure context and a valid certificate. For local work:
  • Serve the page over HTTPS. getUserMedia, WebTransport, and WebCodecs all require a secure context.
  • Use a short-lived ECDSA dev certificate (≤14 days). Pass its SHA-256 to the transport via serverCertificateHashes. The QUIC handshake rejects RSA certs.
  • Dial 127.0.0.1, not ::1. The IPv6 loopback breaks the WebTransport handshake.
See WebTransport and Browser audio troubleshooting for the full setup and common failures (no downlink audio, mic refused, one-way audio).
Native mobile (Swift / Kotlin) apps are on the roadmap, not shipped. There is no native mobile SDK today. A browser tab on the device is the supported path. Track the plan in Mobile apps.

Browser audio capture

The capture pipeline in depth: loopback PC, the worker transform, framing.

JavaScript SDK

Full signatures for Voice, attachCaller, captureMicrophone, OpusPlayer.

Turn detection

Tune VAD, barge-in, and idle timeouts for the agent leg.

Support handoff

Escalate the browser call from the AI agent to a human. The sid stays the same.