Skip to main content
Connect a browser to your stored agent in three steps:
  1. Your server calls GET /v1/token with your API key to mint a short-lived temporary token.
  2. Your browser opens the WebSocket with ?token=<token>, no API key exposed.
  3. The browser sends one session.update with your agent_id; the agent’s stored prompt, voice, and tools load automatically.
Your API key never leaves your server. Each token is single-use, it starts exactly one session, and all usage is attributed to the key that generated it.
This page connects to a stored agent by agent_id — the recommended path. If you’d rather configure the agent inline per session instead of creating one, send system_prompt / greeting / output in the session.update and omit agent_id. The two are mutually exclusive. See Inline configuration.
Browsers provide built-in acoustic echo cancellation through getUserMedia, so browser-based clients work hands-free without headphones. If you’re developing on a laptop, the browser integration is the recommended starting point.

1. Generate a token on your server

Call GET /v1/token with your API key in the Authorization header. Pick an expires_in_seconds short enough to limit replay risk (60–300s is a good default) and an optional max_session_duration_seconds to cap the session length.
These two parameters control different things and are easy to confuse:
  • expires_in_seconds is the token redemption window: how long the client has to use this token to open a WebSocket. If the window elapses before the WebSocket is opened, the server returns a session.error with code unauthorized on the first frame instead of session.ready. Once a session.ready has been received, this value no longer applies.
  • max_session_duration_seconds is the session duration cap: how long the resulting voice agent session is allowed to run after the WebSocket is open.
expires_in_seconds must be between 1 and 600. max_session_duration_seconds must be between 60 and 10800 (defaults to 10800, the 3-hour maximum session duration).
Tokens are single-use — fetch a fresh one immediately before every connection, including reconnects via session.resume. End sessions cleanly with session.end so you don’t pay for the 30-second resume grace window.

2. Connect from the browser with the token

Fetch the token from your server, open the WebSocket with ?token=<token> (no Authorization header needed), and bind to your agent by agent_id:

3. Browser quickstart

A complete working example that captures microphone audio, streams it to the Voice Agent API, and plays back the agent’s response. This requires two files, an HTML page and an AudioWorklet processor.
AudioWorklet processors load from a URL, so this needs two files. Serve them locally with npx serve ..
Create pcm-processor.js in the same directory as your HTML file:
Then create your HTML file:
The key line is new AudioContext({ sampleRate: 24000 }). Most browsers default to the device sample rate (usually 48 kHz), so without this you’d need to manually resample both mic input and playback output. Forcing 24 kHz on the context avoids this entirely. Safari ignores this option and runs at the hardware rate. See Browser compatibility for a Safari-safe pipeline.

4. Browser compatibility

The quickstart above works as-is on Chromium-based browsers (Chrome, Edge, Brave, Arc) and Firefox. Safari has a known quirk that produces silently garbled audio if you don’t account for it.

Safari: resample inside the worklet

Safari ignores the sampleRate constructor option, so an AudioContext({ sampleRate: 24000 }) will silently run at 48 kHz on most Macs. Sending those samples to the Voice Agent API as if they were 24 kHz produces audio that sounds chipmunked or garbled. Detect the actual context rate at runtime, send it into the worklet, and resample there:
For playback, createBuffer(1, length, 24000) works on all current browsers — the context resamples on output. Linear interpolation is good enough for speech.

Cross-browser checklist

  • User gesture required. All major browsers gate getUserMedia and AudioContext startup behind a user gesture (Safari is strictest). Start audio inside a click or touchstart handler and call await audioCtx.resume() before connecting nodes.
  • HTTPS or localhost. getUserMedia only works on secure origins.
  • Echo cancellation. Pass echoCancellation: true to getUserMedia so the agent’s TTS playing through the speakers doesn’t get re-captured by the mic.
  • Audio output sink. On iOS Safari, set the <audio playsinline> attribute or route through an AudioContext destination. Autoplay and full-screen behavior differ from desktop.

5. Ending the session cleanly

Do not just close the WebSocket. Send session.end first, then close. A bare ws.close() (or the browser tearing the socket down on navigation) leaves the session in the 30-second session.resume grace window, and that window is billable.
In the browser, wire session.end to both your explicit end-call control and the pagehide event so tab close and navigation are covered. See Ending the session cleanly on the Voice Agent API overview for the full pattern and code sample.