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.