JWT user identification

Identify signed-in users with JWT

⚡ New — Available in PR #729

If your app already knows who the user is — they’re signed in — pass that identity into the chatbot widget so it can personalize replies and avoid asking again. JWT user identification uses an HMAC-signed token your backend generates, which the Hilal Chatbot backend verifies before trusting the claims.

In this guide:

  • Why use verified identity
  • The three identity states
  • Configure a per-bot HMAC secret
  • Generate a JWT in your backend
  • Pass the token to the widget
  • Verify it’s working
  • Security notes

Why use verified identity

Without JWT, the widget treats users as either:

  • Anonymous — no name, email, or ID known.
  • Self-reported — your host page passed data-user-name / data-user-email, but anyone could fake those.

With JWT:

  • Verified — the user’s identity is cryptographically signed by your backend with a secret only you and Hilal Chatbot share. The chatbot trusts these claims for personalization, the agent dashboard shows the verified badge, and the chat API records X-Hilal-Identity-Source: verified on every message.

Step 1: Configure the HMAC secret

In your chatbot detail → Basic / Appearance (look for the Widget Security card):

  1. Click Regenerate to create a fresh 64-character hex secret.
  2. Copy and store it somewhere your backend can read (env var, secrets manager).
  3. Click Save.

The secret is stored encrypted at rest. The UI shows a masked preview only — the full value is shown once when generated, so copy it now.

Widget Security card Screenshot: The Widget Security card with the HMAC secret regeneration UI.

Single-tenant fallback: if you don’t yet have backend support for a per-bot secret, you can set the env var WIDGET_JWT_HMAC_SECRET on your Hilal Chatbot deployment instead. The per-bot secret takes precedence when configured.

Step 2: Generate a JWT in your backend

Use any standard JWT library. Sign with HS256 and your secret. Required claims:

  • sub — your internal user ID.
  • exp — expiration timestamp (Unix seconds). Recommend ≤ 1 hour from issue.

Recommended additional claims:

  • name — user’s display name.
  • email — user’s email.

Node.js example:

const jwt = require('jsonwebtoken')

function widgetTokenFor(user) {
  return jwt.sign(
    {
      sub: user.id,
      name: user.fullName,
      email: user.email,
      exp: Math.floor(Date.now() / 1000) + 3600
    },
    process.env.HILAL_WIDGET_HMAC_SECRET,
    { algorithm: 'HS256' }
  )
}

Python example:

import jwt, time
def widget_token_for(user):
    return jwt.encode(
        {
            "sub": user.id,
            "name": user.full_name,
            "email": user.email,
            "exp": int(time.time()) + 3600,
        },
        os.environ["HILAL_WIDGET_HMAC_SECRET"],
        algorithm="HS256",
    )

Generate the token server-side, render it into your page (or fetch it via an authenticated endpoint), and pass it to the widget.

Step 3: Pass the token to the widget

Two ways:

Via embed <script> data attribute:

<script
  src="https://cdn.hilalsoftware.tools/chat_v3.js"
  data-org-id="org_..."
  data-bot-id="bot_..."
  data-user-token="eyJhbGciOiJIUzI1NiIs..."
></script>

Via init() config:

window.HilalChat.init({
  organizationId: 'org_...',
  botId: 'bot_...',
  userToken: 'eyJhbGciOiJIUzI1NiIs...'
});

When the widget mounts, it POSTs the token to /api/widget/verify-jwt. The backend verifies the signature using the bot’s HMAC secret. On success, the verified claims (sub, name, email) flow into the chat session as the user identity.

Step 4: Verify it’s working

Open your site in DevTools → Network:

  • Find the POST /api/widget/verify-jwt request after page load. Status 200, response { verified: true, claims: { sub, name, email } }.
  • After opening the chat and sending a message, the chat API request includes the header X-Hilal-Identity-Source: verified.
  • In the agent dashboard, the conversation row shows the verified badge.

If verification fails:

ReasonMeaning
invalid_signatureWrong secret used to sign, or token tampered with.
expiredThe exp claim is in the past. Issue fresh tokens (≤ 1 hour).
not_configuredThe bot has no HMAC secret set. Configure one.
malformedToken isn’t a valid JWT structure.

The widget falls back gracefully — invalid tokens get treated as anonymous, the chat still works.

Security notes

  • Never expose the secret client-side. Sign tokens server-side only. A leaked secret means anyone can forge identities.
  • Rotate periodically. Click Regenerate quarterly or after any suspected leak. Existing tokens immediately fail.
  • Short exp claims. A 1-hour token is fine for most apps. Longer tokens widen the leak window.
  • HS256 only. RS256 / ES256 / asymmetric algorithms are not supported. alg: none is rejected unconditionally.
  • Don’t log tokens. Treat them like passwords.
  • HTTPS only. Tokens travel from your backend to the user to Hilal Chatbot — over HTTPS only. The browser blocks mixed-content.

Troubleshooting

  • Always falls back to anonymous. Open Network → check the verify-jwt response. Common causes: wrong secret, expired exp, mis-typed alg.
  • Verified locally, anonymous in prod. You configured the secret on the dev bot, not the prod bot. Each chatbot has its own secret.
  • Used to work, suddenly fails. Did someone rotate the secret? Coordinate with whoever owns Hilal Chatbot in your team.

What’s next