Skip to main content

Sign the JWT

The contract is the same everywhere: read your user’s session, then sign a new JWT (HS256) with your signing key and the visitor’s email. Start with your language, then check your auth provider for how to read the user.
Always sign server-side. The signing key never belongs in frontend code or a public repository.

Let Your AI Add It

Most teams wire this up with an AI coding assistant. Copy the prompt below into yours (Cursor, Claude Code, Copilot, and similar). It inspects your stack first, asks if anything is unclear, then writes the signing code for you.
Prompt for your AI assistant
You are integrating the BestChatBot chat widget into THIS project. Work in two phases.

PHASE 1 - Inspect first, do not write code yet. Report:
1. The stack: language, framework, and how the app is served (server-rendered pages vs SPA with a separate API).
2. Where a user becomes authenticated: the auth/session library, and where the current user's email is available SERVER-SIDE.
3. Where the BestChatBot widget loads: a <script src="https://widget.bestchatbot.io/widget/v1/chat.js" data-api-key="..."> tag, or where it should be added.
If the stack or auth flow is unclear, ASK me before continuing.

PHASE 2 - Implement visitor identity, following this EXACT contract.

CONTRACT
- Mint a NEW JWT in the BACKEND only. Algorithm: HS256.
- Sign it with a secret read from the env var BESTCHATBOT_SIGNING_KEY. Never hardcode it. Never expose it to the browser.
- Claims:
  - email (REQUIRED): the authenticated user's email, lowercased.
  - exp (REQUIRED): now + 1 hour (the hard maximum is 24h).
  - name (recommended): the user's display name.
  - user_id (optional): the user's id in this system.
- Do NOT reuse the auth provider's session token (Clerk/Supabase/Firebase/Auth0/etc.). Always sign a fresh token.
- Read the email from the verified server-side session, never from client input.
- Only mint a token for authenticated users. For anonymous visitors, send no token.

DELIVERY (pick the one that matches this app)
- Server-rendered: add data-user-token="<jwt>" to the widget <script> tag, rendered per request.
- SPA / client-side: add an endpoint (e.g. GET /api/widget-token) that returns { token }, then call
  window.BestChatBot.setUserToken(token) after login and window.BestChatBot.clearUserToken() on logout.

CONSTRAINTS
- Use the idiomatic JWT library for this stack.
- Touch only what this feature needs. List the files you changed.
- After implementing, tell me to set BESTCHATBOT_SIGNING_KEY (I will paste the signing key from the BestChatBot dashboard).
The signing key is created in your widget settings (Security / Identity Verification). Paste it as the BESTCHATBOT_SIGNING_KEY secret when your assistant asks.
Prefer to do it by hand? The exact code per language and provider follows.

Any Backend

Required: email (lowercase) and exp. Recommended: name. The signing key comes from a backend secret (BESTCHATBOT_SIGNING_KEY).
const jwt = require("jsonwebtoken");

const token = jwt.sign(
  {
    email: user.email.toLowerCase(), // required
    name: user.name,                 // recommended
    user_id: String(user.id),        // optional
  },
  process.env.BESTCHATBOT_SIGNING_KEY,
  { algorithm: "HS256", expiresIn: "1h" } // exp required, max 24h
);
Then hand token to the widget with setUserToken(token) or the data-user-token attribute. See Visitor Identity.

By Auth Provider

Each provider differs only in how you read the signed-in user. Once you have the email, sign the token with one of the snippets above.
Easiest path: install the BestChatBot plugin, paste your signing key in its settings, and it injects the widget and mints the token for you (including WooCommerce email and phone). No code needed.To do it by hand, sign in the footer for logged-in users:
use Firebase\JWT\JWT;

add_action('wp_footer', function () {
    $user = wp_get_current_user();
    if ($user->ID === 0) return; // anonymous, send nothing

    $token = JWT::encode([
        'email'   => strtolower($user->user_email),
        'name'    => $user->display_name,
        'user_id' => (string) $user->ID,
        'exp'     => time() + 3600,
    ], BESTCHATBOT_SIGNING_KEY, 'HS256');

    // inject the widget script with data-user-token = $token
});
Inline tokens can be cached by page caches. The plugin avoids this by fetching the token through a non-cached request. Prefer the plugin on cached sites.
Read the Clerk user server-side, then sign your own token.
import { currentUser } from "@clerk/nextjs/server";

const user = await currentUser();
if (!user) return null; // anonymous

const email = user.primaryEmailAddress?.emailAddress;
// sign with { email, name: user.fullName, user_id: user.id } using "Any Backend"
Do not forward Clerk’s session token. Mint a new JWT signed with your BestChatBot signing key.
Verify the Firebase ID token first, then sign.
const admin = require("firebase-admin");

const decoded = await admin.auth().verifyIdToken(firebaseIdToken);
// sign with { email: decoded.email, name: decoded.name || decoded.email } using "Any Backend"
The frontend sends the Firebase ID token to your endpoint; your endpoint returns the BestChatBot JWT.
Read the user with getUser() (not getSession()), then sign.
const { data: { user } } = await supabase.auth.getUser();
if (!user || user.is_anonymous) return null;
// sign with { email: user.email, name: user.user_metadata?.full_name, user_id: user.id }
Do not forward Supabase’s access token. Sign a separate JWT with your signing key.
Read the Auth0 session server-side, then sign.
const session = await auth0.getSession();
if (!session) return null;
const { email, name, sub } = session.user;
// sign with { email, name, user_id: sub } using "Any Backend"

Next Steps

Visitor Identity

The contract, security rules, and how to deliver the token.

Members & Roles

Control who on your team can manage the widget.