الأدلة
التعرّف على العملاء المسجّلين
دع المساعد يثق بهوية من سجّل الدخول: انشر مفتاحك العام، واجعل واجهة البرمجة لديك توقّع إثباتًا قصير الأجل، واربط 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:
REQUIRED AUTH + HISTORY ACCEPTANCE — Busymate AI / bro
Setup progress is configuration evidence; it does not prove this workflow.
[ ] 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.