Are you an LLM? You can read better optimized documentation at /docs/embedding/user-context.md for this page in Markdown format
User Context & Identity
When Priya lands on your support page, the widget starts a conversation — but the AI has no idea who she is until you tell it. By passing a user context object, you give the AI her name, email, or any custom field so it can greet her by name, scope lookups to her account, and pick up where she left off.
This page is for developers who embed the Perfox widget. You'll walk away knowing which fields to pass, what trust levels mean for your experience, and how that identity reaches your own backend tools. If you haven't embedded the widget yet, start with the window.Perfox SDK.
The shape of a user context object
Every field is optional — pass any subset that you have:
ts
interface WidgetUserContext {
name?: string; // display only, NOT identity
phone?: string; // E.164 preferred; loose formats are normalized
email?: string;
external_id?: string; // your stable customer anchor
attributes?: Record<string, string>; // free-form string→string map
user_hash?: string; // server-side HMAC (verified sessions)
tenant_session_token?: string; // opaque token forwarded to your MCP tools
}What each field does
| Field | Role |
|---|---|
name | Display only — not used for identity resolution. |
phone | E.164 (+919876543210) preferred, but loose formats are normalized. |
email | Identity hint; can resolve to an existing customer record. |
external_id | The stable customer anchor. It lets the agent recognise the same customer across conversations and channels, and it is the field HMAC verification signs. |
attributes | Free-form string → string map. Carry any of your own custom fields here. |
user_hash | The server-computed HMAC that elevates a session to verified — see Identity Verification. |
tenant_session_token | An opaque token forwarded to your MCP tools as the X-Sa-User-Token header, so a visitor who is already signed in to your site skips an in-chat login. Sent verbatim; we never read, store, log, or display it. |
Trust levels
Every widget request that carries an identity gets a trust level (/init reports it as auth_level):
| Trust level | Condition |
|---|---|
anonymous | No external_id supplied. |
self_asserted | external_id supplied without a user_hash. Refused when Require HMAC identity verification is on. |
verified | external_id supplied with a valid user_hash. A user_hash that doesn't match is refused. |
Separately from the trust level, every conversation starts unauthenticated, even when a channel signals an identity — a caller ID or phone number is a low-trust hint, not proof of who's on the line. Marking a conversation authenticated is your explicit decision, made through the auth broker. Per-customer data your tools guard behind login only becomes available after authentication is confirmed.
Worked example — scoping a lookup by account
Setup. Acme Diagnostics has an internal customer ID (cust_abc123) for every registered patient. When a logged-in patient opens the chat, the page already knows who they are.
Action. The page calls identify with the patient's details:
js
window.Perfox('identify', {
name: "Priya Sharma",
email: "priya@example.com",
phone: "+919876543210",
external_id: "cust_abc123",
attributes: { branch_id: "branch-456", plan_tier: "gold" },
user_hash: "<server-generated HMAC>"
});Result. On Priya's first message, the AI greets her by name. When it calls an Acme Diagnostics tool to fetch her report history, Perfox forwards her identity as request headers:
X-Sa-End-User-Name: Priya Sharma
X-Sa-End-User-Email: priya@example.com
X-Sa-End-User-Phone: +919876543210
X-Sa-End-User-External-Id: cust_abc123
X-Sa-End-User-Attributes: {"branch_id":"branch-456","plan_tier":"gold"}
X-Sa-User-Token: <your tenant_session_token, verbatim>What just happened. The tool handler at Acme Diagnostics reads X-Sa-End-User-External-Id and X-Sa-End-User-Attributes, parses the attributes JSON, and scopes the database query to branch_id: "branch-456" — without relying on the AI model to remember or repeat those values. Priya's data never mingles with another customer's.
How identity travels through a session
The context object is sent on every /init and /send call, saved onto the conversation, and forwarded to your MCP tool servers on each tool call as the headers shown above. Calling Perfox('update', …) mid-session — say, after Priya logs in — refreshes her context on the next message, so the AI immediately has her verified identity.
X-Sa-End-User-Attributes is the JSON serialization of your attributes map. Read it server-side in your tool handler and parse it — so your tools can scope every lookup to the right account without relying on the model to pass values through.
tenant_session_token is per-request, not per-conversation
X-Sa-User-Token carries whatever you passed as tenant_session_token, unchanged. It is deliberately never stored: it rides a tool call only on the turns whose identify / message payload carried it. So it is absent after a page reload until identify runs again, and absent on turns the agent starts on its own. If you want a credential that persists for the whole conversation without the page re-supplying it, have your tool server set one through the auth broker instead.
user_hash and tenant_session_token are independent and answer different questions: the hash proves to Perfox that a claimed external_id is really yours, while the token is a credential for your own tools. Neither implies the other, and neither takes precedence. The one interaction to know: with Require HMAC identity verification on, an external_id sent without a valid user_hash is rejected before anything is forwarded, so the token never reaches your tools.
Restoring a conversation
On reload. The widget remembers the visitor's current conversation in their browser for about two hours. Reopen the page inside that window and the whole thread is replayed — not just the last few messages. After it lapses, the next visit starts fresh.
Nothing gates that replay. The Show conversation history switch on the Web Chat trigger is a different feature: it adds the sidebar listing a person's other past conversations.
Across devices. Call identify with the same external_id (or phone, or email) on the new device and the visitor picks up the conversation they already had — because the thread follows the person, not the browser. Three conditions:
- they must be identified; an anonymous visitor's session lives only in that one browser;
- the conversation must still be open — starting a new chat, or resolving the old one, closes it, after which they see a fresh thread and reach the old one through the History list;
- it must be inside your workspace's retention window.
A signed user_hash is not required to resume across devices unless your site enforces identity verification. It is required to list someone's past conversations.
Voice changes none of this: even in the mode where an animated orb replaces the live transcript, spoken turns are replayed as ordinary text afterwards.
Where to go next
You can now pass rich customer context to the widget and trust it will reach your tools on every call.
- Identity Verification (HMAC) — generate the
user_hashserver-side to elevate sessions toverifiedand unlock per-customer features such as the History list. - Authentication & Auth Broker — decide when and how to stamp a session as fully authenticated, and gate sensitive tools behind it.