Authenticated Visitors (JWT Identity)
By default, web chat visitors are anonymous. If your website or mobile app has its own login, you can carry that identity into the chat: your backend signs a small JWT for the logged-in user, the widget presents it once, and the conversation is bound to that user — same history on every device, no re-verification, and your agents see who they're talking to.
Auth modes
Open Integrations → Website → your integration → Authentication:
| Mode | Behavior |
|---|---|
| Anonymous visitors | Anyone can chat. Visitors pass a bot check (Cloudflare Turnstile). Default. |
| Anonymous + authenticated | Anonymous visitors allowed; visitors carrying a valid JWT are recognized as logged-in users. |
| Authenticated only | Only visitors with a valid JWT can chat — for apps behind your own login. |
Enforce identity (only meaningful in Anonymous + authenticated): when on, a visitor presenting an invalid or expired JWT is rejected instead of falling back to an anonymous session.
The signing secret
The Authentication section manages a per-integration signing secret:
- Generate — the secret is shown once. Copy it into your backend's secret manager.
- Rotate — a new secret is issued; the previous one keeps working for 24 hours so your servers can roll over without an outage.
- Revoke — both secrets stop working immediately. If the integration required authentication, it falls back to anonymous mode.
The secret must never appear in website JavaScript, a mobile app binary, or any client-side code. Mint tokens on your backend and hand the finished JWT to the client.
Minting the JWT (your backend)
Sign an HS256 JWT with the integration's secret:
// Node.js — npm i jsonwebtoken
const jwt = require('jsonwebtoken');
const orkiUserJwt = jwt.sign(
{
sub: user.id, // your app's stable user id (this becomes the chat identity)
tenantId: '<your tenant id>',
integrationId: '<your integration id>',
name: user.name, // optional — pre-fills the customer profile
email: user.email, // optional
},
process.env.ORKI_WEBCHAT_SIGNING_SECRET,
{ algorithm: 'HS256', expiresIn: '1h' }, // exp is required; maximum 24h
);
Required claims: sub, iat, exp (≤ 24 h ahead), tenantId, integrationId — the last
two must match the integration the token is used with.
Using the token
Website embed:
webchat('init', { tenantId, integrationId, userJwt: orkiUserJwt });
// later, e.g. shortly before expiry — no reload:
webchat('updateToken', { userJwt: freshJwt });
// on logout:
webchat('logout');
The page fires webchat:auth-required when the widget needs a (fresh) token, and
webchat:user-set / webchat:auth-error after each attempt.
React (@orki/webchat-react): pass userJwt to <OrkiWebChat />, or use
useOrkiWebChat().setUser / updateToken / logout.
Mobile apps: see the React Native and Flutter SDKs.
Custom chat UI: see the Customer Chat API.
How it works (and what it never does)
The JWT is presented once, when the chat session starts. Orki verifies the signature against your integration's secret and issues its own short session credential — your JWT never appears in URLs and never travels with chat messages. A verified visitor bypasses the bot check (your backend's signature is a stronger signal than a captcha). If an anonymous visitor logs in mid-conversation, their existing chat is upgraded to the verified identity — history preserved.