指南
识别已登录的顾客
让助手确信谁已登录:发布您的公钥,让您的 API 签发一份短时效凭证,并接好 getIdentity 与 refreshIdentity。
本页内容
Busymate AI recognizes your signed-in customers without sharing an account database. So the assistant can trust who is asking, your API signs a short-lived proof for each signed-in customer (a launch token — an ES256 JWT valid for 120 seconds with a one-time nonce), you publish the matching public key (JWKS) as the workspace's identity provider, and the widget calls getIdentity and refreshIdentity. bro then serves that customer's history and account tools — only theirs.
Two identities
- Your team signs in to the Console with their own accounts to manage the workspace.
- Your customers never get a platform account. Each launch carries a short-lived proof your product signed; its subject is your unchanging customer id. The claims prove who is chatting — they are not an account database.
1. Keys
Create an ES256 key pair. Publish the public key at https://yourdomain/.well-known/jwks.json with a kid; keep the private key on your server. ES256 is the reference algorithm; the allowed list is part of the provider registration.
2. Register the provider
Open Console → Identity (or call upsert_tenant_identity_provider) and enter:
| Field | Value |
|---|---|
| Issuer | your origin, for example https://yourdomain |
| JWKS URL | https://yourdomain/.well-known/jwks.json |
| Audience | busymate-ai |
| Workspace claim | tenant_id, equal to your workspace id |
| Subject claim | sub — the unchanging internal customer id |
| Max proof age | at most 120 seconds |
| Mint endpoint | the URL of the endpoint from step 3 |
Save the draft, run the checks, publish. An incomplete sign-in setup blocks the release.
3. The mint endpoint
Your API exposes one endpoint that requires your own signed-in session and returns a freshly signed proof for that customer:
import { SignJWT, importJWK } from "jose";
// POST https://YOUR-PRODUCT-DOMAIN/api/bmai/identity — requires YOUR OWN logged-in product session.
app.post("/api/bmai/identity", requireSession, async (req, res) => {
// Fresh on EVERY mint. Never persist the launch token/nonce in localStorage,
// sessionStorage, cookies, React state, or module state: every assistant
// launch consumes this pair exactly once.
const nonce = typeof req.body?.nonce === "string" ? req.body.nonce : "";
if (!/^[A-Za-z0-9_-]{32,200}$/.test(nonce)) return res.status(400).json({ error: "invalid_nonce" });
const key = await importJWK(JSON.parse(process.env.AI_LAUNCH_PRIVATE_JWK), "ES256");
const token = await new SignJWT({
"tenant_id": "<your-tenant-id>", // Busymate AI's registered tenant claim
nonce, // equals the sibling field below
name: req.user.displayName, // optional low-sensitivity display claim
})
.setProtectedHeader({ alg: "ES256", kid: process.env.AI_LAUNCH_KEY_ID })
.setIssuer("https://YOUR-PRODUCT-DOMAIN (set when you register the provider)")
.setAudience("busymate-ai")
.setSubject(req.user.id) // IMMUTABLE internal account id; never email/phone/session id
.setJti(crypto.randomUUID()) // one-time (replay-protected)
.setIssuedAt()
.setExpirationTime("120s") // <= registered max age (120s)
.sign(key);
res.set("Cache-Control", "no-store");
res.status(201).json({ token, nonce, expiresIn: 120 });
});- The nonce arrives from the widget and must match
^[A-Za-z0-9_-]{32,200}$; the response echoes it. - Claims:
iss,aud,sub, your workspace claim,nonce, a one-timejti,iat,expwithin the registered max age. - Respond
201with{ token, nonce, expiresIn }andCache-Control: no-store. Every launch consumes the pair exactly once.
4. Wire the widget
Define window.BusymateAI.getIdentity before the embed script loads. It returns a fresh { token, nonce } for a signed-in customer, or null for a signed-out one. Call refreshIdentity() after login, logout, token rotation and every account switch — it reloads the correct visitor or signed-in history. Never keep a token or nonce in storage, cookies or component state. The Console's Integration section renders the full embed snippet with your values.
5. Full-page open
For a hosted page instead of an embed, mint the same pair and open your address with the token and nonce in the URL fragment — never the query string, referrer or logs. The destination strips the fragment before the exchange. The hosted-handoff snippet in Integration shows the exact form; see also White-label SDK → Customer identity.
6. Acceptance checklist
Setup progress is evidence of configuration; it does not prove the flow works. Run this list before you call sign-in done.
Start with Console → Identity → "Test identified launch" on your provider row. It runs the real preflight against your registration: it fetches your JWKS through the outbound guard and checks that a usable key exists for every algorithm you registered (ES256 needs an EC P-256 key), pushes a deliberately unsigned probe carrying your own issuer, audience and claims through the same verifier the live launch uses — which must refuse it — and reports whether the provider is in the current draft and in the published revision. A 404 JWKS URL, a key set with no matching key, a mistyped issuer or audience, or an enabled-but-unpublished provider all show up here as a red arm with the exact reason, instead of as a customer whose session quietly degrades to anonymous. The same check now decides the publish gate's identified-launch, so a red here blocks the release rather than shipping with it.
To prove the last step end to end, mint a real token with your own endpoint and paste it with its nonce: the test then verifies that exact assertion against your registered provider and reports pass/fail with the claim names it checked. It never stores or echoes the token, the subject, or any claim value. test_tenant_identity_provider is the MCP twin and runs the identical check.
REQUIRED AUTH + HISTORY ACCEPTANCE — Busymate AI / bro
Setup progress is configuration evidence; it does not prove this workflow.
[ ] Console -> Identity -> "Test identified launch" is GREEN on your provider row.
(It fetches your JWKS and checks a key exists for every algorithm you
registered, then pushes a deliberately unsigned probe through the same
verifier the live launch uses and requires it to be REFUSED, and confirms the
provider is in the draft AND the published revision. Do not eyeball the form.)
[ ] Signed out: getIdentity returns null and the assistant remains anonymous.
[ ] Login without reloading the host page: call refreshIdentity(); the frame becomes identified.
[ ] Every getIdentity call returns a different nonce AND JWT jti. No launch token or nonce is persisted.
[ ] The subject is the same immutable internal Busymate AI account id across sessions/devices — never email, phone, browser id, or session id.
[ ] Send an identified message; refresh the host page; the same conversation reappear.
[ ] That refresh produces no launch_replayed, invalid_token, or silent anonymous fallback.
[ ] Logout: call refreshIdentity(); account data is unavailable and the frame is anonymous.
[ ] Login again as the same account: call refreshIdentity(); that user's identified history returns.
[ ] Switch to a second account: call refreshIdentity(); it cannot see the first user's history or data.
Do not rely on a post-message-only identify() flow. Use refreshIdentity() after login,
logout, access-token/session rotation, and every account switch.Pitfalls
- An email, phone number or session id as
sub. Use the unchanging internal id. - A token or nonce kept in
localStorage, a cookie or React state. - A proof older than the registered max age, or a missing
kid. - Answering
getIdentityfor a signed-out customer with anything butnull.
Verify
- Signed out: the assistant is a visitor session.
- Log in without reloading the page and call
refreshIdentity(): the frame is identified. - The same customer on a second device sees the same history.
- A second account cannot see the first account's history or data.
Next
- Connect your MCP server as assistant tools — the tools that need this identity.
- In-app AI support for iOS and Android — the same proof through a native bridge.
- White-label SDK → Customer identity — the full contract.
问题
Do you store my customers' accounts?
No. Each launch carries a proof your product signed; the subject is your id. Conversations are keyed to that subject inside your workspace.
Why does the proof expire in 120 seconds?
It is a launch proof, not a session. A fresh one is minted per launch and used once, so a leaked proof is useless within moments.
Which algorithm must I use?
ES256 is the reference. The algorithms your provider accepts are part of its registration, checked against your JWKS.
Can visitors still chat?
Yes, when guest access is on. Signed-out visitors get answers and guidance; account tools need a signed-in customer.