BookbagBookbag

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 Markdown

If 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.

Two methods — use JWT

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.

Never sign in the browser

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.

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.

ClaimRequiredWhat it does
user_id or subYesThe user's stable id. Becomes the contact's external_id.
emailNoSynced to the contact and visible to the agent.
nameNoSynced to the contact and visible to the agent.
phonenumberNoSynced to the contact.
custom_attributesNoAn object of extra fields; merged into the contact's custom attributes.
stripe_accountsNoAn array stored for actions (e.g. Stripe billing) — never exposed to the model.
expRecommendedStandard JWT expiry. Keep it short (≤ 24h).
Sensitive claims stay out of the model's context

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
});
Pre-load before the script

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_id equals the token's user_id/sub, and fills name/email/phone from the claims. Return visits update the same record.
  • Custom attributes mergecustom_attributes are merged into the contact's custom attributes, not replaced.
  • Stripe accounts stored, not exposedstripe_accounts are 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.
  • Expiredexp is in the past.
  • Not yet validnbf is in the future.
Turn on "Require verified identity" only after wiring identify()

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)

Prefer JWT for new work

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, and user_metadata to 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
  }
});
Re-identify on sign in/out

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_id equals 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.

Turn it on only after wiring identify()

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.