Browser client
@voicethere/client connects web and Node runtimes to VoiceThere voice sessions over WebRTC. Use local mode against a runner on your machine, or cloud mode with credentials from the session API.
Install
npm install @voicethere/client
Package matrix: npm packages. Package API: @voicethere/client.
Local mode (developer runner)
Point at a signaling WebSocket on localhost while you develop an agent against a local session worker:
import { connectVoiceSession } from "@voicethere/client";
const client = await connectVoiceSession({
mode: "local",
signalingUrl: "ws://127.0.0.1:8080/ws",
sessionId: "local-dev",
});
client.on("peer-joined", (peerId) => console.log("peer", peerId));Cloud mode (hosted VoiceThere)
Create a client API key in the dashboard (vthc_…) — safe to embed in browser apps. Start a session via the session API, then pass credentials to the client:
const res = await fetch("https://sessions.voicethere.io/v1/sessions", {
method: "POST",
headers: {
Authorization: "Bearer vthc_…",
"Content-Type": "application/json",
},
body: JSON.stringify({ project_id: "<project-uuid>" }),
});
const credentials = await res.json();
const client = await connectVoiceSession({
mode: "cloud",
credentials: {
sessionId: credentials.session_id,
joinToken: credentials.join_token,
signalingUrl: credentials.signaling_url,
roomId: credentials.room_id,
iceServers: credentials.ice_servers,
},
});Deploy your agent first — see Quickstart. For production sites, add your page origin on the project Browser CORS origins Access panel so browsers can call the sessions API.
Browser imports
import {
connectVoiceSession,
connectChatSession,
} from "@voicethere/client/browser";connectChatSession adds DataChannel text chat alongside voice when your project supports both.
Embed widget
For the dashboard Widget tab, CDN-published appearance, and embed workflow, see Embed widget designer.
import { createVoiceThereWidgetAsync } from "@voicethere/client/embed";
await createVoiceThereWidgetAsync({
clientKey: "vtc_…",
projectId: "your-project-uuid",
apiBase: "https://sessions.voicethere.io/v1",
mode: "chat",
configUrl: "https://cdn.voicethere.io/widgets/w_…/config.json",
});Floating chat launcher for quick demos without building a full UI. Copy the full CDN snippet from Project → Access after Deploy, or omit configUrl for inline-only appearance. After the upcoming @voicethere/client release, you can mount with clientKey and mode only on production — the client fetches bootstrap JSON from the CDN. See Embed widget designer → Key-only embed.
Nested iframes and permissions
When you embed the widget inside another page, set allow="microphone; autoplay" on every ancestor iframe. Browsers block the microphone prompt when a nested iframe lacks Permissions Policy — users often see no dialog at all.
Microphone denied but session connected
Voice sessions can still connect with a silent outbound track when getUserMedia is denied or unavailable. Inbound agent audio does not require a live microphone. The widget shows an amber notice with Request microphone so users can grant access when the browser allows it.
Autoplay and sound
If the browser blocks autoplay, tap Enable sound in the widget after connect (user gesture unlocks the hidden <audio> element).
Voice vs data-only
Use mode: "voice" (or voice+data) for microphone and agent audio. mode: "chat" is data-channel only — no mic capture and no inbound TTS playback path.
Connection drops and reconnect
A voice session that was connected keeps trying to recover after a network drop, for example a Wi-Fi hiccup or a switch between networks. Recovery starts about 3 seconds after the loss: the media connection stays disconnectedfor 2 seconds, fails, or the signaling socket closes unexpectedly. The client then retries with 1, 2, 4 and 5 second backoff. On the platform side the session stays available for the project's reconnect window, so the client can come back to the same conversation.
The client follows the project's reconnect window automatically (15 to 30 seconds, default 15). Set reconnectBudgetMs to override it for one client. When the budget runs out the session ends with WEBRTC_RECONNECT_EXHAUSTED. If the platform ended the conversation while the client was offline, you get SESSION_ENDED_DURING_RECONNECT instead.
Showing the state in your UI
Every onConnectionStatus snapshot carries a recovery field while something is happening:
{ state: "interrupted", sinceMs, message }: the connection dropped and the client is retrying.{ state: "restored", downtimeMs, via, message }: back online.viais"ice-restart"or"reconnect".{ state: "lost", reason, message }: the connection could not be restored and the user has to start a new conversation.
const client = await connectVoiceSession({
mode: "cloud",
credentials,
// reconnectBudgetMs: 30_000, // optional: override the project reconnect window
onConnectionStatus: (status) => {
const banner = document.querySelector("#connection-banner");
if (!banner) return;
if (status.recovery) {
banner.textContent = status.recovery.message;
banner.hidden = false;
} else {
banner.hidden = true;
}
},
});The default messages are "Connection lost. Reconnecting…", "Reconnected.", "The connection could not be restored. Please start a new conversation." and "The conversation ended while the connection was down. Please start a new one." Override any of them with connectionStatusMessages:
connectVoiceSession({
mode: "cloud",
credentials,
connectionStatusMessages: {
interrupted: "Weak connection, trying again…",
restored: "Back online.",
lost: "We lost you. Tap Connect to start over.",
sessionEnded: "The call ended while you were offline.",
},
});Ending a session on purpose
disconnect() and disconnectAsync() send a client_hangup message first, so the platform ends the session at once and billing stops right away instead of the session being held open for a possible reconnect. Closing the browser tab or the connection also ends the session right away.
On the server, a speech server that is restarting or full makes the agent's reply wait for up to 30 seconds instead of ending the conversation. If a reply still cannot be spoken, only that reply is skipped and the conversation goes on. The embed widget shows the same messages in its status line and goes back to Connect when the connection is lost.
Session errors
Handle onSessionError for agent crashes, idle timeout, and recoverable restarts. Error codes are documented in Session errors.
Node / headless
import { createNodeWebRtcRuntime } from "@voicethere/client/node";Pass the runtime into connectVoiceSession when WebRTC is not provided by the browser (automated E2E, load tests).