Roxels/ docs
embed

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):

  1. The embed key isn't allowlisted for this origin. Open the template's embed key settings and add the origin (e.g. https://example.com or http://localhost:3000).
  2. The embed key was revoked. Generate a new one and replace it on the page.
  3. The templateKey is missing or mistyped. Verify the value starts with rk_.

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 persistent mode. 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, use Roxels.init(...) (which renders the launcher) or render your own button and call start() 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 DOMContentLoaded handler:
    document.addEventListener("DOMContentLoaded", () => {
      Roxels.init({ templateKey: "rk-..." });
    });
  • Or load the script with async and use the load event:
    <script
      src="https://app.roxels.ai/embed.js"
      async
      onload="Roxels.init({ templateKey: 'rk-...' })"
    ></script>
  • In React/Next, use <Script strategy="afterInteractive" /> and access window.Roxels only inside useEffect.

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.ready resolved and missed early events. Bind handlers immediately after Roxels.start() returns. Awaiting call.ready is optional.
  • Wrong event name. Common typos: voice-state instead of voice_state, goalTransition instead of goal_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.