Why identity verification
Your widget key is public, so on its own it says nothing about which visitor is calling. Without identity verification, anyone could claim to be any user by sending an arbitraryuserId and read that user’s support history.
For that reason the Halo backend fails closed: any request that names a user (a non-anonymous userId / user_id) is rejected with 403 until your workspace has an Identity Secret, because there is nothing to verify the claim against. Anonymous chat (no userId) works without a secret.
Identity verification is the fix: every user-scoped call includes a JWT signed with your Identity Secret. The Halo backend verifies the signature; if it doesn’t match, the call is rejected with 403.
The JWT can also carry trusted claims — email, name, role, plan, company_id — that Halo will use without a database lookup. This makes the AI more responsive and lets you prove identity even when the user record hasn’t been pushed to Halo yet.
Two keys, two roles
Halo generates two keys when you set up your project:
Copy your widget key from Setup > Install. Generate your identity secret under Settings > Security. Never confuse them — exposing the identity secret in client code defeats the entire system.
JWT payload
The JWT must includeuser_id and may include any additional claims:
Algorithm: HS256. The signing key is your Identity Secret (
ha_secret_...).
Server-side: generate the JWT
Sign the JWT on your backend whenever you serve a page that loads the Halo widget. Pass the resulting token to your frontend (via the page HTML, an API endpoint, or session state).getUserToken or SPA refresh), log errors in your catch blocks before returning 500. Empty catch {} blocks make production debugging much harder.
Client-side: pass the token
Pass the JWT asuserToken when initializing Halo. Always include userTraits with at least name and email so users show up correctly in your inbox:
Token refresh
JWTs expire. The SDK supports two refresh patterns:Recommended: getUserToken (SDK 0.9.0+)
Provide an async callback that fetches a fresh JWT from your backend. The SDK calls it when the static userToken from init() is missing or near expiry (5-minute skew). The identity secret stays on your server.
getUserToken or identify(..., { userToken }) succeeds. Server-side enforce mode is unchanged.
Manual: identify() rotation
You can still rotate tokens yourself on session refresh:
exp matching your app’s session length (e.g. 24 hours) and refresh both at the same time.
Verification modes (dashboard)
After generating an identity secret under Settings > Security, choose how strictly Halo validates tokens:
Stay in monitor while integrating. The Security page tracks production health and unlocks enforce only after verified requests succeed in the last 24 hours with no failures.
Server-to-server calls
The same token applies to direct REST API calls from your backend. For example,POST /api/sdk/chat accepts user_token:
user_id is provided without a valid user_token, the request returns 403. The same 403 is returned when the workspace has no Identity Secret at all; the error body tells you to generate one under Settings > Security.
Best practices
- Always pass name and email in
userTraits(during init), viaidentify(), or as JWT claims. Since SDK 0.10,init()persistsuserTraitsautomatically, and verifiedemail/nameclaims fill in the contact even when no traits are sent. Without any of these, users show as “Anonymous” in tickets and conversations. - Set
expto match your session length — 24 hours is a common default. Shorter is more secure. - Refresh tokens with
getUserToken(preferred) oridentify(..., { userToken })on session refresh. - Wire
onIdentityExpiredso your app refreshes credentials when enforce mode rejects stale JWTs. - Include trusted claims — the more claims (email, role, plan, company_id), the more personalized the AI without extra database lookups.
- Never expose the Identity Secret client-side — keep it in environment variables, never in your HTML or frontend bundle.
Where to go next
Identify Users
The basic identification flow before adding verification.
API Reference
Server-to-server calls with JWT.
Security Settings
Generate your identity secret and set verification mode.