Ultravox Voice Widget
A drop-in browser voice agent — floating mic launcher, live streaming transcripts, shadow-DOM isolated. One script tag, any site.
~508 KB minified
Shadow DOM
Web Components
ESM · CJS · IIFE
Live demo. The mic widget bottom-right is real —
click it, then Start and grant mic access. This page
ships with a guarded
/api/voice/mint Cloudflare
Function: origin-allowlisted, 180s max per call, ~6 calls per
IP per minute, fixed system prompt. For your own site, wire
mintUrl to your own backend.
Drop-in install
<script>
window.UltravoxWidget = {
mintUrl: "/api/voice/mint",
title: "Site helper",
callOptions: { voice: "Mark", maxDurationSeconds: 600 }
};
</script>
<script src="https://THIS-DEPLOY-URL/voice-widget.js" defer></script>
Programmatic
<script src="https://THIS-DEPLOY-URL/voice-widget.js"></script>
<script>
const w = UltravoxWidget.mount({
mintUrl: "/api/voice/mint",
onTranscript: (m) => console.log(m.who, m.text),
});
</script>
Mint endpoint (your server)
The widget never sees your Ultravox API key. Your server trades
it for a short-lived joinUrl:
// POST /api/voice/mint
const r = await fetch("https://api.ultravox.ai/api/calls", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.ULTRAVOX_API_KEY,
},
body: JSON.stringify({
systemPrompt: body.systemPrompt ?? "You are a helpful site assistant.",
model: "fixie-ai/ultravox",
voice: body.voice ?? "Mark",
languageHint: "en",
temperature: 0.4,
maxDuration: `${Math.min(3600, body.maxDurationSeconds ?? 600)}s`,
firstSpeaker: "FIRST_SPEAKER_AGENT",
medium: { webRtc: {} },
}),
});
res.status(r.ok ? 200 : 502).json(await r.json());
Try it
Look bottom-right of this page. Click the launcher, then Start and grant mic access. The agent greets first; transcripts stream in the panel.