Walk your own integration through every moment a signed-in customer has to stay signed in. Nothing is uploaded: the probe reads shapes on your page and reports them back to you.
Open the page that loads the assistant, paste this into the browser console, and press enter. In a mobile app, inject the same snippet through the bridge you already installed.
(async () => {
const api = window.BusymateAI || window.SupportChat;
const shape = (identity) => (identity && typeof identity.token === "string" && typeof identity.nonce === "string"
? { ok: true, token: identity.token.length, nonce: identity.nonce }
: { ok: false });
const result = {
version: "identity-probe/1",
origin: location.origin,
channel: new URLSearchParams(location.search).get("channel"),
apiPresent: Boolean(api),
providerRegistered: Boolean(api && typeof api.getIdentity === "function"),
verbs: api ? Object.keys(api).filter((key) => typeof api[key] === "function") : [],
storage: (() => { try { localStorage.setItem("bm_probe", "1"); localStorage.removeItem("bm_probe"); return "available"; } catch (e) { return "blocked"; } })(),
ready: Boolean(window.__bmaiReady),
mint: { attempted: false },
};
if (result.providerRegistered) {
try {
// TWO mints, on purpose: a provider that answers the same pair twice is
// the cached-token defect, and it is invisible from one call.
const first = shape(await api.getIdentity());
const second = shape(await api.getIdentity());
result.mint = first.ok && second.ok
? { attempted: true, answered: true, distinct: first.nonce !== second.nonce }
: { attempted: true, answered: false, signedOut: !first.ok && !second.ok };
} catch (error) {
result.mint = { attempted: true, answered: false, threw: String(error && error.message || error) };
}
}
console.log("%cBusymate identity probe", "font-weight:bold");
console.log(JSON.stringify(result, null, 2));
try { await navigator.clipboard.writeText(JSON.stringify(result)); console.log("copied to the clipboard"); } catch (e) {}
return result;
})()0 passed · 0 failed · 12 not observed
? First launch arrives identified
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Register `getIdentity` (web) or install the bridge (app) BEFORE the assistant loads. A provider registered later answers a parked ask, but a bridge that was there from the first byte never needs the recovery.
? Every ask gets a FRESH proof
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Remove the cache. A launch proof is single-use; the second launch that replays the first one's token is refused and that customer becomes a guest, intermittently, which is why this reads as flaky rather than broken.
? A signed-out visitor answers `null`
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Return null when there is no session. The assistant then says `not_signed_in` — a different story in your Console from `mint_failed`, and the difference is what tells you whether your backend is down.
? A late bridge is still asked
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Nothing to do on the web — the loader parks an unanswerable ask and flushes it. In an app, register the handler before loading the URL; a handler installed after first paint relies on a bounded watch that eventually gives up.
? A login during an open chat upgrades in place
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Call `identityChanged()` (web/desktop) or `bridge.identityChanged()` (app) from your sign-in handler. Without it the customer signs in on your site and keeps talking as a guest until something reloads the widget.
? A token rotation is signalled too
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Call `identityChanged()` on rotation and on account switch, not only on the initial login. An account switch that is not signalled leaves the previous person's conversation on screen.
? A logout revokes the identity AND the transcript
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Call `signedOut()` (web/desktop) or `bridge.signedOut()` (app) on every sign-out. This is the one obligation whose absence is a privacy defect rather than a missing convenience: the next person at that device sees the previous one's conversation.
? Returning from the background re-asserts identity
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Nothing on the web — the assistant listens for visibility and bfcache restores itself. In an app, call `bridge.onResume()` from your own resume hook so a shell that restores a WebView without firing either event is still covered.
? A restarted chat is still identified
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Keep your provider registered for the life of the page or the WebView. A provider wired inside a component that unmounts leaves the restart with nobody to ask.
? Two tabs are one visitor
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Nothing to do, unless your site strips cookies for the assistant's own host. One browser is one visitor and many tabs are many sessions, by design; grouping two DEVICES is the subject's job, server-side.
? Blocked storage does not block identity
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Nothing to do: identity comes from YOU on each launch, not from a cookie. If a guide ever tells you to relax a cookie policy to make sign-in work, it is not describing this integration.
? A desktop shell says so
Seen: no probe result — run the snippet on your own page and paste what it copied
Fix: Add `channel=desktop` to the URL your shell loads. It also qualifies the shell for the restart hand-off, which is how a desktop app re-acquires identity after a reload.
Your key set, your token and your identity endpoint are checked from a terminal, because a browser cannot read another origin's headers. The same rules, the same cell names, machine-readable with --json.
node v2/scripts/identity-conformance.mjs --jwks https://yourdomain/.well-known/jwks.json --token - --nonce <nonce>One result reads oddly until you know why: an identity endpoint on the same origin as your page correctly refuses a foreign origin, and the checker reports that as a pass rather than telling you to open a hole you do not need.