Identifying users
An anonymous visitor is a conversation with no history. Telling the widget who is signed in turns it into a customer record your agents recognise — and signing that claim is what stops anyone else from wearing it.
The call
Call it once the user is known, and again whenever that changes. Only id really matters; everything else improves what the agent sees.
Support.identify({
id: "usr_123",
email: "ada@example.com",
name: "Ada Lovelace",
userHash: serverGeneratedHmac, // never ship the secret itself
metadata: { plan: "pro", accountId: "acc_456" }
});- idstring
- Your own user id, up to 120 characters. The customer is matched on this before anything else, and this is the value the HMAC signs.
- emailstring
- Used to match a customer when no id was given, so chat and email land on one record.
- namestring
- Display name, up to 150 characters.
- userHashstring
- Hex HMAC-SHA256 of id, keyed with the project identity secret. Computed on your server, never in the browser.
- metadataobject
- Anything worth seeing beside the ticket: plan, account id, role, seat count.
Which customer the visitor becomes
The id wins: a visitor claiming one joins the customer already holding it. With no id, the email address is used instead. With neither — or with a claim that could not be proved while verification is on — they get a record of their own rather than joining anyone else's.
Why the signature exists
identify() runs in the browser, so on its own it is a claim, not a fact. Anyone can open the console and claim to be your biggest customer. The fix is an HMAC: your server signs the user id with a secret only it knows, your page passes the signature through as userHash, and the widget's server checks it.
Send it down with the rest of the page data — in the same response that already says who is signed in. It is not a secret: it is worthless for any other user id, and useless to anyone who cannot already sign in as that user.
// server-side only — Node.js
import { createHmac } from 'node:crypto'
export function supportUserHash(userId) {
return createHmac('sha256', process.env.HELPWING_IDENTITY_SECRET)
.update(userId)
.digest('hex')
}# server-side only — Python
import hashlib
import hmac
import os
def support_user_hash(user_id: str) -> str:
return hmac.new(
os.environ["HELPWING_IDENTITY_SECRET"].encode(),
user_id.encode(),
hashlib.sha256,
).hexdigest()<?php // server-side only — PHP
function support_user_hash(string $userId): string {
return hash_hmac('sha256', $userId, getenv('HELPWING_IDENTITY_SECRET'));
}Any language is the same one line: hex HMAC-SHA256 over the user id, keyed with the identity secret from Chat widget → Identity. That secret belongs on your server only — not in a bundle, not in a repository, not in an environment variable your frontend build can read.
Turning verification on
It is off by default, and the switch is on the same screen. What changes:
Off
An unsigned claim is taken at face value and the visitor is matched to that customer — but the record is never marked verified, so an agent can see the difference.
On
An unsigned claim buys nothing. The visitor is treated as the anonymous visitor they proved themselves to be, and what they claimed is kept beside the ticket as a claim rather than as a profile.
An unproved claim is never a reason to refuse the conversation. Refusing would keep nobody out — dropping the identify() call gets you in as an anonymous visitor either way — and would only cost you the message. So turning verification on cannot break support for your users; it can only stop one of them from being mistaken for another.
Before you switch it on, test a hash you generated against the project on the same screen. A mismatch there is much easier to read than a visitor quietly arriving as a stranger.
Signing out
Pass null. There is no separate reset call, and nothing else is needed — the widget stores whatever it was last given, and a falsy value clears it.
// Signing out. The visitor keeps the conversation in front of
// them; what they lose is the link to the account.
Support.identify(null);On a shared computer, remember that the conversation itself is stored in the browser: the next person to use it can still read what was said, exactly as they could with any other tab left open. Signing out unlinks the account, it does not wipe the device.