Checklist & troubleshooting
What to verify before you ship a WebView integration, and the failure modes worth recognising on sight.
Before you ship
| # | Check | Done when |
|---|---|---|
| 1 | URL carries all four container flags | embedded=true&mode=sdk&platform=<ios|android>&isMobile=true |
| 2 | platform matches the real device | Analytics land on the right mobile channel |
| 3 | Bridge object exists and is named exactly loopsAIBridge | You receive ready |
| 4 | Bridge is installed before page content loads | ready arrives on the very first load, not only after a reload |
| 5 | initContext is sent from your ready handler | Even {} — flows that wait for it can open |
| 6 | The user can close the chat | Your own control, or closeButtonEnabled: true — and closeChat dismisses your container |
| 7 | openExternalUrl is routed | Product taps open the in-app browser (or a native screen you opted in) |
| 8 | Attribution survives | Commercially important links open in a web context, not a native screen |
| 9 | The WebView cannot navigate off chat.loopsai.com | Foreign navigations are cancelled and routed out |
| 10 | Sender is verified | Main frame + chat host (native) or event.origin (iframe) |
| 11 | Storage is persistent | Reopening resumes the same conversation |
| 12 | persistSession ids are stored and replayed | The conversation survives an app kill |
| 13 | Camera / microphone are wired if you offer try-on or voice | Both flows complete on a real device |
| 14 | Android file chooser is implemented if you offer try-on | Photo-library upload opens a picker |
| 15 | Rotation sends mobileStateChange | Layout re-flows on rotate |
| 16 | Buttons are gated on the agent's flags | No button that silently does nothing |
| 17 | Availability is checked before showing your entry point | Chat can be turned off from the dashboard |
| 18 | Language matches your app | ?locale= sent when your app pins a language |
| 19 | Nothing sensitive in the URL or in context | No tokens, no personal records |
Troubleshooting
There is no ✕ in the chat header
That is the mode=sdk default — the runtime assumes your container draws its own
close control. Send initConfig with closeButtonEnabled: true to turn the web
one on, or present your own and dismiss the container yourself.
The ✕ is there but nothing happens when I tap it
Your bridge is missing, misnamed, or installed too late. The runtime found no
loopsAIBridge, fell back to window.parent — which in a top-level WebView is the
page itself — and the message went nowhere.
- Check the exact name and casing:
loopsAIBridge. - On iOS it must be a
WKUserContentControllerhandler:window.webkit.messageHandlers.loopsAIBridge. - Everywhere else it must be
window.loopsAIBridgewith apostMessage(string)method. - In a WebView you can inspect, run
typeof window.loopsAIBridgein the console —"undefined"is your answer.
I never receive ready
mode=sdkis missing from the URL. Without it the runtime treats a top-level page as standalone and never attaches its listener.- Or the bridge was injected after page scripts ran. Inject at document start
(
injectedJavaScriptBeforeContentLoaded,WKUserScriptat.atDocumentStart, an early JavaScript channel registration).
The commands I send are ignored
- Iframe transport: you did not pass
parentOrigin(URL-encoded) on the iframesrc. The runtime only trusts its own origin, the referrer, andparentOrigin. - You sent before
ready. There is no queue on the web side — early messages are simply lost. - The envelope is malformed. It needs
type: "nativeAction"and aname, with arguments insidepayload. - Android only: you passed an object instead of a JSON string, or forgot
JSON.stringify.
The size guide never opens
You have not sent initConfig or initContext. The body-type guide waits for the
host to finish initializing. Send initContext (even {}) from your ready
handler.
suggestSize does nothing
The shopper's height and weight are already on record and there is no
productCode in context, so there is no product to size. Send updateContext
with a productCode first.
Try-on or voice mode opens and then stalls
A permission is missing. Three layers all have to agree:
- The OS permission for your app (camera / microphone).
- The WebView's own request —
requestMediaCapturePermissionForon iOS,onPermissionRequeston Android. - In an iframe:
allow="camera; microphone"on the element.
If try-on stalls specifically on photo upload on Android, you are missing
WebChromeClient.onShowFileChooser — the file input silently does nothing without
it.
Product taps open inside the chat WebView
mode=sdk is missing, so the runtime is navigating for itself instead of emitting
openExternalUrl. Add it, and add a navigation guard so a foreign URL can never
load in the chat WebView.
The conversation restarts every time
- You recreate the WebView with a non-persistent data store. Use
WKWebsiteDataStore.default()and enable DOM storage on Android. - Or the WebView's storage was cleared between launches. Store the ids from
persistSessionnatively and replay them as_lsuid/_lscid.
The chat renders but nothing is configured — default colours and copy
The agent's published configuration could not be fetched. Check the agentId in the
path, and check that the device can reach chat.loopsai.com (a corporate proxy or
certificate-pinning policy is the usual culprit).
The chat is in the wrong language
Without ?locale= the chat follows the device language, which is not always your
app's language. Pass ?locale=en / ?locale=tr (regional tags like tr-TR are
accepted) — it outranks the device and any stored preference, and it also sets the
language the assistant answers in. Language is resolved on load, so a warm WebView
has to be reloaded with the new value.
The chat does not render at all
Ask for the availability endpoint —
GET /api/embed/<agentId>/style.
If the channel is Inactive, activate it in the dashboard.
Analytics never arrive
trackEvent is only emitted when your agent's mobile channel is configured for
native-bridge delivery. Ask your Loops AI contact to enable it — see
Analytics.
Everything works, but reopening the chat is slow
You are destroying the WebView on close. Hide your container instead of tearing it
down: the conversation, scroll position and connection stay alive and reopening is
instant. Handle ready, persistSession and trackEvent at container level so
they still work while the chat is off screen.
Still stuck?
Send your Loops AI contact the exact URL you load (minus session ids), the first five messages you receive from the bridge, and the platform + WebView version. That is almost always enough to spot the problem.
Where to go from here
- Mobile SDKs — if you can adopt them later, they replace this whole page with a few lines.
- Canonical events — the analytics taxonomy your forwarded events belong to.
- Widget Embed — the same runtime on the web, if you also integrate a website.