Skip to content

Verify signed-in users

Keep signed-in customers' chats on every device, and tell the AI agent who they are.

Last updated

When someone is signed in to your site, the widget can know who they are. Your server signs a short-lived token for them, and the widget sends it with every chat request. Then:

  • Their conversations follow them to every device and browser where they're signed in.
  • Nobody else can open their conversations.
  • The AI agent gets their name and email as facts you vouch for, not as text anyone could type.
  • In Inbox, their conversations show their name with a Verified tag.

Visitors who aren't signed in keep chatting anonymously, as before.

What you need#

  • The deployment's Secret key, under Parameters on its Install tab. Select the eye icon to reveal it. Keep it on your server: anyone who has it can sign in as any of your users.
  • An endpoint on your server that only a signed-in user can reach, such as GET /api/help-ai-token, which returns their token as plain text.
  • Widget 1.10.0 or later. The Install tab's snippets load a version that has it.

The Install tab's Verify signed-in users card has these steps with the same snippets, ready to copy. It isn't shown when Embed in an iframe is on.

1. Sign a token on your server#

The token is a JWT signed with HS256 and the deployment's secret key. No other algorithm is accepted.

ClaimWhat it is
subRequired. Your own id for the user, as text, up to 255 characters
expRequired. When it expires, in seconds since 1970 (not milliseconds). There's no upper limit, but keep it to an hour: the widget fetches a new one when it runs out
nameOptional. Their name, up to 200 characters, for your team and the AI agent
emailOptional. A valid email address

Leave out a claim you don't have. A null or empty name or email makes the whole token invalid. Tokens longer than 4,096 characters are refused.

Node#

js
import { SignJWT } from 'jose';

// Serve it from an endpoint only your signed-in
// user reaches, e.g. GET /api/help-ai-token
export function signHelpAiToken(user) {
  const secret = new TextEncoder().encode(
    process.env.HELPAI_SECRET_KEY,
  );
  // Leave out what you don't have: an empty
  // name or email makes the token invalid.
  const claims = {};
  if (user.name) claims.name = user.name;
  if (user.email) claims.email = user.email;
  return new SignJWT(claims)
    .setProtectedHeader({ alg: 'HS256' })
    .setSubject(String(user.id))
    .setExpirationTime('1h')
    .sign(secret);
}

Python#

python
import os, time
import jwt  # PyJWT

# Serve it from an endpoint only your signed-in
# user reaches, e.g. GET /api/help-ai-token
def sign_help_ai_token(user):
    claims = {
        "sub": str(user.id),
        "exp": int(time.time()) + 3600,  # an hour
    }
    # Leave out what you don't have: an empty
    # name or email makes the token invalid.
    if user.name:
        claims["name"] = user.name
    if user.email:
        claims["email"] = user.email
    secret = os.environ["HELPAI_SECRET_KEY"]
    return jwt.encode(claims, secret, algorithm="HS256")

PHP#

php
<?php
use Firebase\JWT\JWT; // firebase/php-jwt

// Serve it from an endpoint only your signed-in
// user reaches, e.g. GET /api/help-ai-token
function signHelpAiToken($user): string
{
    $claims = [
        'sub' => (string) $user->id,
        'exp' => time() + 3600, // an hour
    ];
    // Leave out what you don't have: an empty
    // name or email makes the token invalid.
    if (!empty($user->name)) {
        $claims['name'] = $user->name;
    }
    if (!empty($user->email)) {
        $claims['email'] = $user->email;
    }
    $secret = getenv('HELPAI_SECRET_KEY');
    return JWT::encode($claims, $secret, 'HS256');
}

2. Give the widget the token#

Give the widget a function that fetches the token, not the token itself. The widget calls it when it first needs a token, and again when help.ai turns a token down, for example once it has expired. If your function fails, the widget carries on as an anonymous visitor.

With the Quick install script tag, call login on each page while your user is signed in. The first line lets the call run before the widget's script, for example inline in your page; it then waits for the widget:

js
window.HelpAI = window.HelpAI || function () { (HelpAI.q = HelpAI.q || []).push(arguments); };

HelpAI('login', () =>
  fetch('/api/help-ai-token').then((res) => res.text()),
);

With another install, where getToken is that same function:

  • <web-ai-chat> element: document.querySelector('web-ai-chat').login(getToken)
  • NPM package: pass userToken: getToken to init(), or call chat.login(getToken) after sign-in.
  • React: <HelpAI publicKey="…" aiAgentDeploymentId="…" userToken={getToken} />

Pass the same function each time for the same user. Calling login again with a different function, for example a new arrow function on every route change of a single-page app, counts as a different user and starts the widget over. In React, define getToken outside your component, or with useCallback keyed on your user's id.

You can also pass the token itself, as text. The widget can't refresh it then: once it expires, chat requests fail until you call login again.

3. Sign them out#

When your user signs out, start the widget over so the next person on that computer sees nothing of theirs:

js
HelpAI('reset');

Call it while the widget is on the page, for example in your sign-out button's click handler. On <web-ai-chat> or an NPM install it's chat.reset(); in React, drop the userToken prop. Their conversations stay with their account for the next time they sign in.

What your team and the agent see#

  • In Inbox, the conversation shows their name, or else their email, or else your sub id, with a Verified tag. Hover over the tag to see the id they signed in with.
  • The AI agent gets their id, name and email as verified facts, never as instructions.

Good to know#

  • Conversations a visitor started on this browser before signing in move to their account when they sign in.
  • Signing in as a different user starts fresh, the same as signing out first.
  • What the AI agent remembers from their earlier conversations follows them too, on any device. The next person on a shared browser starts without it.
  • To give the agent more about them, such as their plan, see Tell the AI agent about your visitor and page. Those details aren't verified.

Troubleshooting#

  • Requests fail with 401 USER_TOKEN_INVALID: the token is malformed, signed with another key or algorithm, expired, or has a name or email that's empty, null or not valid. Each deployment has its own secret key. The widget asks your function for a fresh token and tries once more. A token that's turned down doesn't fall back to an anonymous chat: requests fail until your function returns a valid one.
  • New tokens fail straight away: check your server's clock. There's no leeway, so if you add iat or nbf, a clock that runs ahead makes the token look like it comes from the future.
  • Chats don't follow the user to another device: sub must be the same id for the same user everywhere, such as your database id.
  • The token works in a test but not on your site: check that your endpoint returns only the token, as plain text, to the signed-in user, not a login page, JSON or the token in quotes.
  • HelpAI is not defined: your login call runs before the widget's script. Add the first line from step 2.

Was this helpful?

Still need help?

Ask the community