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.
| Field | Where | How you use it |
|---|---|---|
| Agent ID | Settings (labeled Agent ID) | agentId on the widget and aud on the token. Same string in both places. |
| Identity secret | Identity Handoff card → Generate secret | Server-only key used to sign tokens. Shown once. |
| Identity mode | Identity Handoff card: Optional or Required | Same choice as widget identityMode: 'optional' or 'required'. |
1. Turn on Identity Handoff
Open the agent → Settings. On the Identity Handoff card:
- 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. - 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.
- Optional (
- Copy Agent ID from the same Settings page. Use that value as
agentIdand asaud.
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:
- Confirm the user is signed in.
- Sign a JWT with the identity secret and algorithm HS256.
- Return that JWT to the page.
If the user is not signed in, return an error (no token).
Claims to include
| Claim | Required | Value |
|---|---|---|
aud | Yes | Agent ID (same as agentId) |
name | Yes | Display name, non-empty, max 255 characters |
email | Yes | Valid email, max 255 characters |
exp | Yes | Expiry time (Unix timestamp) |
iat | No | Issued-at time (recommended) |
{
"aud": "YOUR_AGENT_ID",
"name": "Jane Smith",
"email": "jane@example.com",
"iat": 1735686000,
"exp": 1735689600
}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:
- Confirm the visitor is signed in.
- Sign the JWT (
aud= Agent ID). - 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 tokenaudmust 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
nameand a validemail, plusexpin 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
agentIdand asaud - JWT is HS256 with
aud,name,email, andexp - First
initincludesidentityProvider(oridentityTokenon a server-rendered page) - If the user is not signed in, the provider throws and no token is returned