Skip to main content

Step 1: Call debug()

When the widget isn’t behaving the way you expect, the first thing to do is grab a snapshot of its current state. Open your browser console on the page where the widget should be and run:
You’ll see something like:
Each line maps to a category of question: The same snapshot is also returned as a serializable object you can pipe to support tooling:
Sensitive fields (userToken, apiKey) are reduced to presence flags, never echoed, so the snapshot is safe to share.

Common issues

”The widget never appears”

Run window.HaloAgents.getInstance()?.debug(). If it returns undefined, the SDK isn’t initialized:
  1. Check that the <script> tag for cdn.haloagents.ai/sdk/latest/haloagents.umd.js is actually loading. Open the network tab, refresh, and look for it. If the request fails with a CSP violation, see Content Security Policy.
  2. Check that window.HaloAgents.init({ orgId: "..." }) is being called. Browser console: typeof window.HaloAgents should be "object".
  3. Confirm init() is called only once. A second call without destroy() is a no-op and logs a warning.
If debug() runs but dom.host=false, the host element was unmounted. Check whether something else on the page is calling destroy().

init() ran but nothing works / getInstance() is undefined”

If you load the SDK dynamically (async, next/script, or document.createElement("script")), onload can fire before window.HaloAgents is defined. Poll for the global before calling init():
See Async loading for a full example.

”The widget appears on marketing pages but not on /app/*”

Check the agent’s Page URL Rules in the dashboard (AI Agents → [Agent] → Channels → Chat Widget → Configure). If a rule excludes the current path, the widget unmounts entirely. debug() would return undefined in this case. If debug() returns a snapshot but dom.trigger=false, you’ve likely set ui: { trigger: false } or ui: 'manual'. Run debug() and look at ui flags to confirm.

”Identify isn’t working / users show as Anonymous”

Run debug() and check the user= line. Three failure modes:

”My in-app message didn’t show (hidden widget / Contact Support button)”

In-app messages render independently of the floating launcher. They do not require the chat panel to be open. Common causes:
  1. init() was deferred until the user clicked Contact Support. In-app messages only fetch after init() runs. Call init() on page load and use openChat() on your button click instead.
  2. The embedder opted out. Run HaloAgents.getInstance()?.debug() and check ui flags.inAppMessages. If it’s false, the host set ui: { inAppMessages: false } (or pinned an SDK version before 0.9.0 with ui: 'manual').
  3. Audience / identify. Segment- or field-targeted messages require identify() with a matching user. Check user= in the debug() output.
  4. Previously dismissed. In-app dismissals persist in localStorage under haloagents_dismissed_{orgId}.

”I want zero overlays in manual mode (SDK ≥ 0.9.0)”

Since 0.9.0, ui: 'manual' and manualOnPaths allow dashboard banners and in-app messages by default. To restore the old behavior, pass a ui object with explicit opt-outs:
Run debug() and confirm banners=false and inAppMessages=false.

”I set ui: 'manual' but a banner still showed up (SDK < 0.9.0)”

In SDK 0.5.1 through 0.8.x, ui: 'manual' suppressed banners and in-app messages. If you’re on ≥ 0.9.0, that is expected: manual mode now allows dashboard outreach. Upgrade intentionally or add the opt-out flags above if you want zero overlays.

”Console says ‘HaloAgents is already initialized’”

HaloAgents.init() was called twice without an intervening destroy(). The second call is a no-op and returns the existing instance, so the widget continues working with the first config (not the second). In SPAs this usually means init() is being called inside an effect that re-runs. Either:
  • Move init() to a singleton guard outside React/Vue lifecycles, or
  • Call HaloAgents.getInstance()?.destroy() before the second init() if you genuinely want to reinitialize with new config.

”Chat works on localhost but is blocked in production”

Almost always CSP. Open the production page, open DevTools console, look for messages mentioning “Content Security Policy” or “Refused to connect”. The fix is documented at Content Security Policy.

”Chat works in production but is blocked on localhost”

Inverse of the CSP case, and the cause is almost always Allowed Domains (origin lockdown). When that feature is on, every SDK request from a domain not in the list is rejected with a 403, and localhost will never match a production hostname. Open DevTools → Network. If the failing request is GET /api/sdk/config or POST /api/sdk/events with status 403 and a body like:
You’re hitting origin lockdown. Two fixes:
  1. Add a loopback entry to your allow-list. Go to Setup → Install → Allowed Domains in the dashboard and add 127.0.0.1 or localhost. Then run your dev server on the host you added (http://127.0.0.1:3000 or http://localhost:3000).
  2. Disable Allowed Domains while developing. Toggle it off in the dashboard. The widget will accept any origin until you re-enable it. Identity Verification (the per-user JWT defense) keeps working independently.
See Origin allow-list for the full model.

”Voice doesn’t work / microphone errors”

Microphone access requires Permissions-Policy: microphone=(self) on the page (or the parent if the SDK is iframed). See Microphone permissions.

”I see ‘config issue’ warnings on init”

SDK 0.6.0+ validates the config object you pass to init() and surfaces three classes of issue as a single grouped console.warn. The widget keeps working in every case; the warning just makes mistakes visible that previously failed silently. Typo. A common typo gets a “did you mean?” suggestion when there’s a near match:
Fix the spelling. Without the fix, the feature you intended to use is silently disabled. Wrong shape. The validator catches obvious type mistakes for the small set of fields where the wrong shape silently disables a feature instead of throwing:
Fix the shape (e.g. wrap a single pattern in an array: manualOnPaths: ["/app/*"]).

Reporting a bug

When something genuinely seems wrong, the most useful bug report includes:
  1. Output of HaloAgents.getInstance().debug() (full snapshot, structured object).
  2. The pathname where the issue reproduces.
  3. Browser console errors (red), if any.
  4. What you expected vs. what happened.
Email this to [email protected] or open a ticket from the Help & Support page.