Troubleshooting
Common issues and how to resolve them. If your symptom isn't here, reach out at support@roxels.ai.
The widget loads but the mic is blocked
Symptom: the call starts but the agent never hears the user. The error event may fire with microphone_blocked.
Cause: your host is sending a Permissions-Policy header that doesn't allow the embedded iframe to use the microphone.
Fix: add the Roxels origin to the microphone directive of the header. See Microphone permissions for the snippet for your host (Cloudflare, Nginx, Apache, Vercel, Next.js, CloudFront).
"Session creation failed" on start
Symptom: error event fires with session_create_failed immediately after start().
Causes (most common first):
- The embed key isn't allowlisted for this origin. Open the template's embed key settings and add the origin (e.g.
https://example.comorhttp://localhost:3000). - The embed key was revoked. Generate a new one and replace it on the page.
- The
templateKeyis missing or mistyped. Verify the value starts withrk_.
The floating button doesn't appear
Symptom: the script loads (no console errors) but no button shows up.
Causes:
- The template's launcher is not configured for
persistentmode. The button appears only when the template enables it. See Persistent launcher for setup. - You called
Roxels.start()instead of relying on the launcher.start()opens the conversation immediately and doesn't render a button. If you want a button, useRoxels.init(...)(which renders the launcher) or render your own button and callstart()from its click handler. - The current page is excluded by the launcher's visibility rules. Check the include/exclude path globs — see Persistent launcher § visibility rules.
The widget opens but never connects
Symptom: the widget appears, the status stays at joining or loading, no agent voice.
Causes:
- Network or firewall blocking LiveKit. LiveKit uses WebRTC over UDP and falls back to TURN over TCP. Some corporate networks block both. Test on a different network to confirm.
- The session timed out before connect. Sessions have a maximum unjoined duration. If your code created the session a long time ago and only just opened the embed, create a fresh session.
Roxels is not defined
Symptom: your JS throws ReferenceError: Roxels is not defined.
Cause: the <script src="https://app.roxels.ai/embed.js"> hasn't loaded yet when your code runs.
Fixes:
- Put your code in a
DOMContentLoadedhandler:document.addEventListener("DOMContentLoaded", () => { Roxels.init({ templateKey: "rk-..." }); }); - Or load the script with
asyncand use theloadevent:<script src="https://app.roxels.ai/embed.js" async onload="Roxels.init({ templateKey: 'rk-...' })" ></script> - In React/Next, use
<Script strategy="afterInteractive" />and accesswindow.Roxelsonly insideuseEffect.
Multiple calls running unexpectedly
Symptom: more than one microphone is hot, more than one agent is speaking.
Cause: you started a new call without closing the previous one.
Fix: always call controller.close() (or controller.hangUp() for graceful) before starting a replacement. In React/Vue, do this in the unmount lifecycle.
Headless: events not firing
Symptom: in headless mode, you bound to call.on("chat", ...) but nothing happens.
Causes:
- You bound after
call.readyresolved and missed early events. Bind handlers immediately afterRoxels.start()returns. Awaitingcall.readyis optional. - Wrong event name. Common typos:
voice-stateinstead ofvoice_state,goalTransitioninstead ofgoal_transition. Event names use snake_case. See Events.
Headless: the microphone keeps recording after the call ends
Symptom: the mic LED stays on after you thought the call was done.
Cause: you didn't call controller.close().
Fix: call controller.close() on unmount or after complete. The complete event signals the conversation has ended, but the iframe stays around for cleanup until you close it.
React strict mode mounts the call twice
Symptom: in React Strict Mode (dev only), useEffect runs twice on mount, so you see two calls start.
Fix: track whether a call is already mounted with a ref, and skip the second mount:
const callRef = useRef(null);
useEffect(() => {
if (callRef.current) return;
callRef.current = Roxels.start({ display: "none", templateKey });
return () => {
callRef.current?.close();
callRef.current = null;
};
}, [templateKey]);The transcript is missing earlier turns
Symptom: in headless, the user scrolled away or refreshed and lost transcript history.
Cause: chat events are not replayed. The embed delivers them as they arrive.
Fix: in your handler, append to a persistent store (local state, a backend, localStorage) as messages arrive — don't rely on the embed to re-emit history.
Audio is choppy or the agent sounds robotic
Symptom: the agent's voice has gaps, distortion, or robotic artifacts.
Causes:
- Network bandwidth or jitter. Voice WebRTC needs a stable connection.
- CPU pressure. Browser tabs throttle audio when CPU is constrained. Try with fewer tabs open.
- Output device issues. Bluetooth headsets occasionally drop frames.
If the issue reproduces on a known-good network, please share the session id (visible via call.sessionId or in your dashboard) with support@roxels.ai so we can look at the per-leg diagnostics.
"Too many redirects" on /docs
This is a docs-site issue, not an embed issue. If you see this, it's our problem and is likely temporary — please email support@roxels.ai.
Read next
- Microphone permissions — Host-side Permissions-Policy setup.
- Persistent launcher — Floating button config.
- Headless mode — Full guide if you're building a custom UI.
- Events — What each event means.