Turn anonymous visitors into known customers.
One identify call attaches an email, a contact record, and (optionally) a company to everything that visitor does — chats, page views, product events.
When to identify
Call identify once per session, right after your app knows who the user is — after login, signup, or when restoring a session on page load. Anonymous visitors before that point are tracked automatically and merged into the identity.
From the widget
HalWidget.identify({
user_id: "usr_123", // recommended: your stable id
email: "mia@novaleaf.com",
name: "Mia Kovač",
company: { name: "Nova Leaf", domain: "novaleaf.com" },
plan: "trial"
});From your server
When the identification happens in a backend flow (signup API, webhook from your billing provider), call the identify route directly — no widget required:
curl -X POST "https://api.chatwithhal.com/api/widget/identify" \
-H "Content-Type: application/json" \
-d '{
"project_id": "YOUR_PROJECT_ID",
"user_id": "usr_123",
"email": "mia@novaleaf.com",
"name": "Mia Kovač",
"metadata": { "plan": "trial" }
}'What HAL creates
- Contact — created or updated from
email/user_id. - Company — created from
company.name/company.domainand linked to the contact. - Visitor session — current and future page visits attach to the identity.
- Metadata — flat properties (strings, numbers, booleans) stored on the visitor, visible in the inbox context rail.
Field behavior
Matching order is user_id first, then email. If neither matches an existing contact, a new one is created. Traits merge into the existing record — set() sends partial updates without replacing everything:
HalWidget.set({ seats: 12 }); // merge one property
HalWidget.set({ plan: "scale" }); // ...another laterIdentify vs set vs track
- identify() — "this is who I am." Creates/updates the contact. Once per session.
- set() — "here are current property values." Merges traits on the known identity.
- track() — "this just happened." Appends a timestamped event, changes nothing.
Stage sync semantics
When your identify payload or setStage() includes a CRM stage, HAL applies it in advance-only mode by default: stages can move forward along your pipeline order but never backward. This protects won deals from being demoted by a UI regression or stale client state. Pass "exact" as the second argument when a backward move is intentional (e.g. marking churn).
Verify identities
Anyone can call identify with any email from the browser console. If your site acts on identities — showing plan data, moving deals — sign them server-side with HMAC identity verification and optionally reject unsigned claims.