Visitor authentication

Prove who a visitor is with a signed token, and let your agent call your own APIs on their behalf.

Visitor authentication

By default, when your page tells Lyro "this visitor is [email protected]", that is a claim from a browser and nothing more. Anyone can open devtools and say the same thing about someone else. That is fine for personalising a greeting and not fine for showing an order history.

This page covers the two mechanisms that fix it, and how they differ.

What it answersWho checks it
Signed identity (user_jwt)Who the visitor isLyro
Auth tokens (setAuthTokens)A credential to call your backend as that visitorYour backend
API keys (krns_pk_)Who your application isLyro - see API and developers

They are independent. You can use either, both, or neither.

Signed identity

A signed token says who the visitor is, and Lyro verifies the signature before believing it. A verified identity is what makes an email trustworthy enough to look up an account against.

Available on the two distributions with a frontend of your own - the chat widget and the API (krns_pk_). Other channels already carry a provable identity from the provider.

1. Choose how visitors are verified

Open your widget or API distribution and go to the Security tab. Two options:

Shared secret (HS256)Login provider (RS256)
Who signsYour backendYour login provider - Cognito, Auth0, Okta, Entra, Keycloak, Firebase, Clerk
What you configureOne shared secretThe provider's issuer URL and client ID
Lyro storesThe secretNothing - keys are fetched from the provider
Backend workA signing endpointNone: the page forwards the token your provider already issued
RotationInstant, from the dashboardYour provider rotates; Lyro follows

Shared secret is the default and the simplest when you do not run a login provider. Both sides hold the same value, which means the secret can sign as well as verify - so treat it like a password. Lyro can generate one, shown exactly once, or you can supply your own from your KMS.

Login provider is the right choice when your users already sign in through an OpenID Connect provider. Enter the issuer and the client ID from the provider's console, press Connect, and Lyro confirms it can reach the provider before saving. From then on the ID token your provider issues is the visitor's identity: Lyro checks its signature against the provider's published keys and accepts it only for that issuer and that client. Because there is no key to leak on either side, connecting a provider also switches Require a signed identity on and keeps it on.

Some providers do not fit. Supabase Auth signs with a shared secret by default, so it stays on the shared-secret path. Providers that issue only opaque tokens cannot be connected.

2a. Login provider: pass the provider's token

Nothing to sign. Hand the widget the ID token, as a getter so it is re-read before every request - provider tokens are short-lived and your site refreshes them:

// The token lives in a cookie your page can read:
lyro('boot', {
  user_jwt: () => document.cookie.match(/(?:^|;\s*)id_token=([^;]+)/)?.[1],
});

// Or your login SDK holds it:
lyro('boot', { user_jwt: () => auth.getIdToken() });

On the API distribution, send the ID token as user_jwt on POST /api/chat.

Lyro takes the visitor's id from the token's sub claim and the email only when the provider marks it email_verified. Other claims are ignored unless you list them under Import claims; listed ones become verified properties with any punctuation in their names replaced by underscores, so custom:plan is {{user.claims.custom_plan}}.

A token that has expired makes the visitor anonymous for that request - the chat keeps working without account data. The widget skips a token it can see is expired and fires a lyro('on', 'identity_expired', cb) event once, in case your page wants to refresh. If a different account logs in on your site, the widget starts a fresh session for the new person rather than mixing the two.

2b. Shared secret: sign a token

The claims:

// BACKEND ONLY - never expose the key to the browser.
import jwt from "jsonwebtoken";

const user_jwt = jwt.sign(
  {
    user_id: user.id,   // required
    email: user.email,  // optional
    plan: user.plan,    // any extra key becomes a verified property
  },
  process.env.LYRO_IDENTITY_SECRET,
  { algorithm: "HS256", expiresIn: "10m" },
);
  • user_id is required. Use your own stable user id - it is the anchor Lyro resolves the contact against.
  • email is optional and, when present, is recorded as a verified email identity.
  • Any other claim becomes a verified property, usable in agent instructions and custom tools.
  • Choose an expiry. Lyro accepts a token without one, but a token that never expires is a permanent assertion sitting in your frontend. Match it to your session length.

Sign on your server, never in the browser. The dashboard's Security tab has copy-paste samples for Node, PHP and Python.

3. Pass it to Lyro

On the widget:

lyro('boot', { user_jwt: '<signed token>' });
lyro('update', { user_jwt: '<refreshed token>' });   // after you refresh the session

On the API distribution, send it as the user_jwt field on POST /api/chat.

Re-supply it whenever you refresh the session, or pass a getter (user_jwt: () => …) and the widget re-reads it before every request. The token is held in memory and Lyro does not renew it. If it expires mid-conversation the visitor is quietly treated as unverified - the chat keeps working, but verified properties stop resolving, which reads as a quality problem rather than an error.

4. Require it (optional)

The Require a signed identity toggle rejects unsigned identity claims, so your page can no longer assert an email without proof. Anonymous visitors can still chat in either setting - they simply have no identity attached.

A valid signed token is honoured whether the toggle is on or off. The toggle governs only what happens to unsigned claims. With a login provider connected it is always on: an identity your site claims without the provider's token would be exactly the impersonation the provider prevents.

Checking a token

Check a token on the Security tab verifies a token against your installed key and shows what Lyro extracts from it. Use it before wiring anything up - it turns a silent "properties are empty" into a specific reason.

ResultUsually means
VerifiedWorking. The panel lists the identity and claims.
Invalid signatureThe token was signed with a different key than the one installed, or by a key the provider no longer publishes.
Wrong algorithmThe token's algorithm does not match the option selected on this tab.
ExpiredWorking, but the token has aged out - check how long yours live.
Missing user_idThe required claim (user_id, or sub for a provider) is absent or empty.
Wrong issuer / audienceA provider token from a different pool or app client than the one connected. Check the values in the provider's console.
Wrong token typeAn access token was sent where the provider's ID token is expected.
Unknown keyThe provider is rotating keys; Lyro refetches them and the next attempt succeeds.

Verified properties

Claims from a verified token are available to your agent and to custom tools as {USER.ID}, {USER.EMAIL} and {{user.claims.<name>}}.

They resolve only when the current turn is trusted. An anonymous or unsigned visitor gets nothing, so a forged identity can never be interpolated into a call to your backend. {USER.VERIFIED} is always answerable and returns true or false, so a tool can branch on it without failing.

To keep a contact property trustworthy after it is stored, mark it Protected in Settings > Properties. A protected property can only be written by a verified source, so an unsigned browser claim can no longer overwrite it. See People.

Auth tokens for your own APIs

Signed identity tells Lyro who someone is. It does not let a custom tool call your API as that person - for that, pass a credential your backend already understands:

lyro('setAuthTokens', { my_api: '<the visitor\'s access token>' });

It becomes {{auth_tokens.my_api}} in any custom tool, most often as an Authorization header. Lyro forwards it verbatim and never parses, stores or logs it.

Because Lyro does not read the token, it cannot tell when it expires - call setAuthTokens again whenever you refresh. Full details in Chat Widget.

Where to go next


Did this page help you?