Signed JWT
The recommended way to identify visitors - a short-lived HS256 token signed with your shared secret
A host-signed JWT is the recommended way to identify visitors. You mint a short-lived token on your server and hand it to the widget. The backend verifies it against your integration's shared secret.
Token Format
| Field | Requirement | Value |
|---|---|---|
| Algorithm | required | HS256 |
| Secret | required | Your integration's shared secret - server-side only |
sub | required | A stable, opaque visitor id (your user's id). user_id is read if sub is missing. It cannot start with google:, passkey:, preview: or wexio-operator:. |
iat | recommended | Issued-at, Unix seconds |
exp | recommended | Expiry, Unix seconds - use 1–24 hours. A token without exp never expires. |
email | optional | Visitor email |
name | optional | Display name |
phone | optional | Phone |
fields | optional | A trusted profile map written to the contact (see below) |
Sign It on Your Server
import jwt from "jsonwebtoken";
const token = jwt.sign(
{
sub: user.id,
email: user.email,
name: user.name,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour
},
process.env.WEXIO_INTEGRATION_SECRET,
{ algorithm: "HS256" },
);Hand It to the Widget
// React
<WexioWidget
publicKey="pk_live_..."
user={{ jwt: serverSignedJwt, name: "Ada Lovelace", email: "ada@example.com" }}
/>// Web Component
document.querySelector("wexio-widget").identify({ jwt: serverSignedJwt, name: "Ada" });
// Script loader
window.Wexio("identify", { jwt: serverSignedJwt, name: "Ada" });The backend verifies HS256 and exp, extracts sub as the user id, and establishes a verified session. A leaked token stops working at exp, so always set it.
Trusted Profile Fields
The whole token is signature-verified, so a fields claim writes the contact's profile server-side. No prechat form is needed.
const token = jwt.sign(
{
sub: user.id,
email: user.email,
name: user.name,
fields: {
company: "Acme", // system field
lead_status: "QUALIFIED", // system select - must be a valid option
plan_tier: "enterprise", // custom field by key
mrr: 4200,
},
iat: now,
exp: now + 3600,
},
process.env.WEXIO_INTEGRATION_SECRET,
{ algorithm: "HS256" },
);The rules:
- Name and contact fields.
namesplits intofirst_name+last_nameat the first space;email/phonemap to the email/phone fields. Explicitfields.first_nameetc. override the split. - Other keys. They write people-field values by key, but only for fields that exist for your org and are writable.
- Skipped silently. Unknown keys, read-only/derived fields (
*_at,total_*,flow_*, …), and protected keys (trust flags, channel ids) are skipped. Invalid values are skipped, never thrown.
Protected keys - web_verified, is_blocked, chat_status, priority, channel ids like telegram_id, and wexio_id - can never be set through the JWT. web_verified in particular is set by the backend only on a real verification, so flows and the AI assistant can trust it.
What the AI Does with Them
These values are signed by your backend, so the assistant can be trusted to act on them. Two features read them:
- Agent selection rules. Route a visitor to a different assistant based on a field such as
plan. - Knowledge audience. Restrict what the assistant may draw on to visitors whose field matches.
Send the fields claim on every sign-in, not only the first. Both features read the current contact, so a plan that changed on your side only takes effect once you have sent a token carrying the new value.
The assistant knows the visitor's sign-in state and plan, and never states either back to them. It answers differently but does not say why.
Rotating Identity
To log in as a different user, pass a new token. To log out, clear it.
<WexioWidget user={{ jwt: newJwt, name: "Grace" }} /> // switch user
<WexioWidget user={undefined} /> // log outRelated
- Sessions & Prefill - renewal and
shutdownbehaviour. - Authentication - compare the identity methods.
- Create a Widget - where the shared secret comes from.