Identity verification
Securely tell the agent who the signed-in visitor is from the embedded widget. Sign a JWT on your server with HS256, call window.bookbag("identify", { token }), and the agent recognizes the customer, auto-syncs a contact, and personalizes replies.
View as MarkdownIf your visitors are already signed in to your site, you can tell the embedded agent who they are. To stop anyone impersonating a user, you sign the visitor's identity on your server and the widget forwards that signed proof with every message. Bookbag verifies it, recognizes the customer, syncs a contact, and personalizes the conversation.
The recommended method is a signed JWT (below): one token carries the user id plus profile claims and an expiry. The older user-hash method (further down) is still supported for Chatbase-style setups, but JWT is more capable and is what new integrations should use.
The token (or hash) must be generated on your server with your agent's verification secret. If you put the secret in client-side JavaScript, anyone can read it and impersonate any user.
JWT method (recommended)
Your server signs a JSON Web Token with HS256 using the agent's verification secret, and your page hands that token to the widget. The token carries the user id and any profile claims you want synced; the widget never sees the secret.
Your verification secret
Each agent has its own verification secret. It's the same secret shown under Deploy → Chat widget → Embed (Identity verification), and the dashboard can fetch it for you. Store it as a server-side environment variable, and Rotate it from the same screen if it's ever exposed (the old secret stops working immediately).
Token claims
Sign a payload with these claims. Either user_id or sub is required — it's the stable id Bookbag keys the contact on. Everything else is optional.
| Claim | Required | What it does |
|---|---|---|
user_id or sub | Yes | The user's stable id. Becomes the contact's external_id. |
email | No | Synced to the contact and visible to the agent. |
name | No | Synced to the contact and visible to the agent. |
phonenumber | No | Synced to the contact. |
custom_attributes | No | An object of extra fields; merged into the contact's custom attributes. |
stripe_accounts | No | An array stored for actions (e.g. Stripe billing) — never exposed to the model. |
exp | Recommended | Standard JWT expiry. Keep it short (≤ 24h). |
Claims like stripe_accounts are stored against the contact for actions to use, but are not put into the agent's context — so the model can't read or leak them. Name/email/phone are synced and the agent can use them to personalize.
1. Sign the token on your server
const jwt = require("jsonwebtoken");
const secret = process.env.BOOKBAG_SECRET; // your agent's verification secret
const token = jwt.sign(
{
user_id: String(currentUser.id), // or `sub`
email: currentUser.email,
name: currentUser.name,
phonenumber: currentUser.phone,
custom_attributes: { plan: "growth", lifetime_value: 4200 },
stripe_accounts: [currentUser.stripeCustomerId], // hidden from the model
},
secret,
{ algorithm: "HS256", expiresIn: "1h" } // sets `exp`
);2. Identify the user in the browser
Render the server-signed token into the page and pass it to identify. Any fields you pass outside the token (like name, age) are also given to the chatbot as context — use them for non-sensitive personalization only.
window.bookbag("identify", {
token, // the JWT from your server
name: "Jane Doe", // outside the token → visible to the chatbot
age: 34, // outside the token → visible to the chatbot
});To identify the visitor before the embed even finishes loading, set the token first and the widget picks it up on boot:
<script>
window.bookbagUserConfig = { token: "THE_JWT_FROM_YOUR_SERVER" };
</script>
<script src="https://app.bookbag.ai/widget/embed" data-agent-id="123" defer></script>Log out
When the visitor signs out, clear the identity so the agent stops treating them as that user:
window.bookbag("resetUser");Contact sync semantics
- Upsert by id — Bookbag finds or creates the contact whose
external_idequals the token'suser_id/sub, and fills name/email/phone from the claims. Return visits update the same record. - Custom attributes merge —
custom_attributesare merged into the contact's custom attributes, not replaced. - Stripe accounts stored, not exposed —
stripe_accountsare saved for actions (e.g. Stripe billing actions) but kept out of the agent's context.
What the server rejects
Verification fails — and, when Require verified identity is on, the chat is refused with a 401 — for any of:
- Wrong algorithm — the token must be signed with HS256; other algorithms are rejected.
- Bad signature — signed with the wrong secret or tampered with.
- Expired —
expis in the past. - Not yet valid —
nbfis in the future.
With it on, unverified, forged, or expired tokens get a 401 and the chat is blocked. Ship identify() first, confirm it works, then require it.
User-hash method (legacy)
The user-hash method below predates JWT and exists for Chatbase-style setups. It still works, but JWT carries more (profile claims, expiry, Stripe accounts) in a single signed token — use it for new integrations.
Instead of a token, you sign just the user id with an HMAC and send the id, hash, and metadata separately. Bookbag verifies the hash and trusts the identity.
How it works
- On your server, compute
user_hash = HMAC-SHA256(user_id)using your agent's signing secret. - On the page, call
window.bookbag('identify', { user_id, user_hash, user_metadata }). - The widget attaches
user_id,user_hash, anduser_metadatato every chat request; Bookbag verifies the hash and trusts the identity.
Your signing secret
Each agent has its own signing secret. Open your agent → Chat Interface → Embed → Identity verification, click Reveal, and copy it. You can Rotate it at any time (the old secret stops working immediately). Store it as a server-side environment variable.
1. Sign the user id on your server
const crypto = require("crypto");
const secret = process.env.BOOKBAG_SECRET; // your agent's signing secret
const userId = String(currentUser.id);
const userHash = crypto.createHmac("sha256", secret).update(userId).digest("hex");2. Identify the user in the browser
Render the server-computed values into the page, then call identify:
window.bookbag("identify", {
user_id: "user-123",
user_hash: "the-hash-from-your-server",
user_metadata: {
name: "Jane Doe",
email: "jane@example.com",
plan: "growth" // any extra keys become contact custom attributes
}
});Call identify again after the user signs in or out. The widget always uses the most recent identity, and clears it if you call identify(null).
What identity unlocks
However you identify a visitor — JWT or user-hash — a verified identity gives you:
- Contact auto-sync — on a verified chat, Bookbag upserts the contact whose
external_idequals the user id, filling name/email/phone and merging any extra attributes into custom attributes. Return visits update the same record. - Personalized answers — the agent receives the contact's known details and can greet the customer by name and tailor replies without re-asking.
- Action context — actions can reference the verified customer with
{{contact.email}},{{contact.name}},{{contact.external_id}}, and{{contact.attr.<key>}}.
Require verified identity (optional)
By default the agent accepts both anonymous and verified visitors. Turn on Require verified identity (Chat Interface → Embed → Identity verification) to reject any chat that isn't signed. For JWT, Bookbag verifies the token (HS256, signature, exp/nbf); for the user-hash method it recomputes HMAC-SHA256(user_id) with your secret and compares it to the user_hash the widget sent. A missing, forged, or expired credential is refused with a 401.
If you require verified identity before your site calls identify() with a valid token, all chats are rejected. Roll it out: ship identify() first, confirm it works, then require it.
Shopify
On Shopify the storefront integration identifies the logged-in customer for you from a theme-injected window.__bbCustomer. See the Shopify integration.