Identity Handoff

Show signed-in visitors by name and email in chat instead of Anonymous.

Identity Handoff attaches your signed-in users to AssistLoop chat. Without it, every visitor appears as Anonymous. With it, conversations show their name and email. The same token can be passed as identity_token when you create a conversation through the API.

Sign tokens only on your server. Never put the identity secret in browser or app code.

What you need

Copy these from the agent's Settings page in the dashboard.

FieldWhereHow you use it
Agent IDSettings (labeled Agent ID)agentId on the widget and aud on the token. Same string in both places.
Identity secretIdentity Handoff card → Generate secretServer-only key used to sign tokens. Shown once.
Identity modeIdentity Handoff card: Optional or RequiredSame choice as widget identityMode: 'optional' or 'required'.

1. Turn on Identity Handoff

Open the agent → Settings. On the Identity Handoff card:

  1. Click Generate secret and store the value on your server (for example ASSISTLOOP_AGENT_IDENTITY_SECRET). The dialog is the only time AssistLoop shows it.
  2. Set Identity mode:
    • Optional (identityMode: 'optional') — chat works without a token; visitors without a token stay Anonymous.
    • Required (identityMode: 'required') — chat does not start until a valid token is provided.
  3. Copy Agent ID from the same Settings page. Use that value as agentId and as aud.

Rotate secret replaces the secret immediately. Update your server with the new value or existing tokens will fail.

2. Mint a token on your server

When a signed-in user is on your site and the widget needs a token, your backend should:

  1. Confirm the user is signed in.
  2. Sign a JWT with the identity secret and algorithm HS256.
  3. Return that JWT to the page.

If the user is not signed in, return an error (no token).

Claims to include

ClaimRequiredValue
audYesAgent ID (same as agentId)
nameYesDisplay name, non-empty, max 255 characters
emailYesValid email, max 255 characters
expYesExpiry time (Unix timestamp)
iatNoIssued-at time (recommended)
Example payload
{
  "aud": "YOUR_AGENT_ID",
  "name": "Jane Smith",
  "email": "jane@example.com",
  "iat": 1735686000,
  "exp": 1735689600
}
Node.js (server only)
import jwt from 'jsonwebtoken';

function mintChatIdentityToken(user, agentId) {
  return jwt.sign(
    {
      aud: agentId,
      name: user.name,
      email: user.email,
    },
    process.env.ASSISTLOOP_AGENT_IDENTITY_SECRET,
    { algorithm: 'HS256', expiresIn: '1h' }
  );
}

Use a short expiry (minutes to about an hour). The widget will request a new token when it needs one.

3. Pass the token to the widget

Load the widget script, then call AssistLoopWidget.init once. Use identityProvider so a new token can be fetched later.

<script src="https://assistloop.ai/assistloop-widget.js"></script>
<script>
  AssistLoopWidget.init({
    agentId: 'YOUR_AGENT_ID',
    identityMode: 'optional',
    identityProvider: async function (context) {
      const response = await fetch('/api/chat-identity-token', {
        method: 'POST',
        credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ agentId: context.agentId }),
      });
      if (!response.ok) {
        throw new Error('User is not signed in');
      }
      const data = await response.json();
      return { identityToken: data.token };
    },
  });
</script>

Set identityMode to 'optional' or 'required' to match the dashboard.

Your token endpoint should:

  1. Confirm the visitor is signed in.
  2. Sign the JWT (aud = Agent ID).
  3. Return JSON such as { "token": "<jwt>" }.

Return value: { identityToken: "<jwt>" }. If the user is not signed in, throw.

  • Optional — chat continues; the visitor stays Anonymous.
  • Required — chat does not start.

context.agentId is the Agent ID you passed to init.

Only the first AssistLoopWidget.init is used. Later calls are ignored. Put identityProvider (or identityToken) on that first call. If you pass both, identityProvider is used.

Token already on the page

For a server-rendered page you can pass the JWT at init:

AssistLoopWidget.init({
  agentId: 'YOUR_AGENT_ID',
  identityToken: '{{ server_rendered_jwt }}',
});

This does not refresh after expiry and does not follow login or logout. Prefer identityProvider when the user can sign in or out in the browser.

Login and logout

Call init once. The identityProvider should always look at the current session (cookie or equivalent).

  • After login, return a JWT the next time the widget asks for a token (for example when the visitor opens chat, or when a previous token has expired).
  • After logout, throw so you do not issue a new token.

Events (optional)

AssistLoopWidget.on tells you whether identity resolved. It returns a function you can call to unsubscribe. Event payloads never include tokens or secrets.

AssistLoopWidget.on('assistloop.widget.identity.success', function () {
  // A token was provided, or Optional mode continued without one
});

AssistLoopWidget.on('assistloop.widget.identity.failed', function () {
  // The provider threw, or Required mode had no token
});

If name and email do not appear

  • Agent ID in Settings, agentId, and token aud must be the same string.
  • Dashboard Identity mode must match identityMode ('optional' or 'required').
  • Sign with the current identity secret and HS256. After Rotate secret, update the server.
  • Include non-empty name and a valid email, plus exp in the future.
  • Generate the identity secret before you mint tokens.

Checklist

  • Generate secret on the Identity Handoff card; keep it on the server only
  • Dashboard mode matches widget identityMode
  • Agent ID from Settings is used as agentId and as aud
  • JWT is HS256 with aud, name, email, and exp
  • First init includes identityProvider (or identityToken on a server-rendered page)
  • If the user is not signed in, the provider throws and no token is returned