Skip to content
Dashboard
Open .md fileOpen llms.txtA list of every page, for AI tools

Guides

Identify users

Sign in your users in the widget, so their conversations follow them and agents see who they are.

app.js
// token: an identity token that your server signed for the signed-in user
Iris.identify({ token });
  1. Your server signs an identity token with your identity secret.
  2. Your page passes the token to the widget with Iris.identify.
  3. Iris checks the signature and links the contact to your user’s sub. Earlier anonymous conversations move to that contact.

An admin copies the identity secret from Settings → Install in the dashboard. Only admins see it. Store it in your server’s environment as IRIS_IDENTITY_SECRET.

.env
IRIS_IDENTITY_SECRET=<your identity secret>

Sign a new identity token on each page load, or cache it until it nears exp. Every claim is in Identity token claims.

server.js
import { SignJWT } from "jose";
// user is the signed-in user in your product.
const token = await new SignJWT({
email: user.email,
name: user.name,
attributes: { plan: user.plan, role: user.role },
})
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.setSubject(user.id)
.setExpirationTime("1h")
.sign(new TextEncoder().encode(process.env.IRIS_IDENTITY_SECRET));

Call Iris.identify after Iris.init. Call it again with a new token before the old one expires. Call Iris.logout when the user signs out.

app.js
// After the user signs in
const { token } = await fetch("/api/iris-token").then((res) => res.json());
Iris.identify({ token });
// When the user signs out
Iris.logout();
Show the test tokenHide the test token

Sign these claims with this identity secret. Your code must print exactly this identity token. If it does not, compare the header and the order of the claims.

Test identity secret
7c1e4b9a2f6d0e8c3b5a1f9d7e2c4a6b8d0f3e5c7a9b1d2e4f6a8c0e2b4d6f8a
Claims
{
"sub": "user_8421",
"email": "maria@example.com",
"name": "Maria Lopez",
"attributes": {
"plan": "pro",
"seats": 12
},
"exp": 1893456000
}
test-token.mjs
import { SignJWT } from "jose";
const secret = new TextEncoder().encode("7c1e4b9a2f6d0e8c3b5a1f9d7e2c4a6b8d0f3e5c7a9b1d2e4f6a8c0e2b4d6f8a");
const token = await new SignJWT({
"sub": "user_8421",
"email": "maria@example.com",
"name": "Maria Lopez",
"attributes": {
"plan": "pro",
"seats": 12
},
"exp": 1893456000
})
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.sign(secret);
console.log(token);
Expected identity token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzg0MjEiLCJlbWFpbCI6Im1hcmlhQGV4YW1wbGUuY29tIiwibmFtZSI6Ik1hcmlhIExvcGV6IiwiYXR0cmlidXRlcyI6eyJwbGFuIjoicHJvIiwic2VhdHMiOjEyfSwiZXhwIjoxODkzNDU2MDAwfQ.0RrzrVC8W-h9B_rrS9KlmwI67hD1bmMnTeaPesGUUmo

Then sign in to your product, open the widget and send a message. In the dashboard inbox, the conversation shows the contact’s name and email.

  • An identity token stops working at its exp. After that, the session request fails with 401.
  • When an admin rotates the identity secret, every token signed with the old secret fails at once. Put the new secret in IRIS_IDENTITY_SECRET and deploy your server right after.
MessageWhat happenedFix
Iris: <method> ran before Iris.init, so nothing changed.#identify, logout or update ran before Iris.init. After an early identify, the contact stays anonymous.Call Iris.init({ appId }) first. Calls made before iris.js loads wait in a queue, in order.
Iris: identify got no identity token, so the contact stays anonymous.#The token is empty or not a string. Often your token endpoint failed.Check that your server returns an identity token, then pass it as Iris.identify({ token }).
401 on the session request#Iris rejected the identity token. It expired, its signature does not match the identity secret, or sub or exp is missing. The console shows Iris: <action> failed, because Iris rejected the identity token.Sign a new identity token with the current identity secret and both required claims. Check your code with the test token.