Authentication

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

FieldRequirementValue
AlgorithmrequiredHS256
SecretrequiredYour integration's shared secret - server-side only
subrequiredA 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:.
iatrecommendedIssued-at, Unix seconds
exprecommendedExpiry, Unix seconds - use 1–24 hours. A token without exp never expires.
emailoptionalVisitor email
nameoptionalDisplay name
phoneoptionalPhone
fieldsoptionalA 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. name splits into first_name + last_name at the first space; email / phone map to the email/phone fields. Explicit fields.first_name etc. 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:

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 out

Was this page helpful?

On this page