Identifying visitors
When a visitor is signed in to your site, you can tell the widget who they are so the conversation links to a real contact in your workspace. There are two ways to attach visitor information, and they are not the same.
Zev.setattaches display details. They are convenient, but unverified. Anyone can set them from the browser.Zev.identifyattaches a verified identity. It proves the visitor is who your site says they are, using a signature only your backend can produce.
Use identify for signed-in customers. Use set for casual context you do not need to trust.
How verified identity works
Section titled “How verified identity works”Verification uses an HMAC signature.
- Your backend has a secret identify key for the brand.
- Your backend signs the visitor’s id with that secret and produces a signature.
- Your page calls
Zev.identifywith the visitor’s id and the signature. - The server recomputes the signature with its copy of the secret. If they match, the visitor is linked to a contact.
The secret stays on your backend. Only the signature crosses into the browser, and a signature cannot be reversed into the secret.
Step by step
Section titled “Step by step”-
Get your identify secret. In the dashboard, go to Settings → Brands and open your brand. Under Verified identity secret, click Reveal, then copy the value. Keep it on your server and never ship it to the browser. You can Rotate the secret at any time; the previous one keeps verifying for 30 days, so you can update your backend without a gap.
-
Compute the signature on your backend. Sign the visitor’s stable id, for example their user id, with HMAC SHA-256.
server.js import crypto from 'node:crypto';const externalId = 'user_12345'; // your stable user idconst hmac = crypto.createHmac('sha256', process.env.ZEV_IDENTIFY_SECRET).update(externalId).digest('hex');// Send `externalId` and `hmac` to the page. The secret stays here. -
Call identify on the page. Pass the same id and the signature your backend produced.
Zev.identify('user_12345', 'the_hmac_your_backend_computed', {email: 'jane@example.com',firstName: 'Jane',lastName: 'Doe',});
Attributes
Section titled “Attributes”The third argument is optional. Use it to attach known details to the contact.
| Field | Type | Description |
|---|---|---|
email | string | The contact’s email address. |
firstName | string | The contact’s first name. |
lastName | string | The contact’s last name. |
metadata | object | Any extra key and value pairs to store. |
Cross-device history
Section titled “Cross-device history”Once a visitor is identified, their past conversations follow them. A customer who chatted on their laptop and later opens the widget on their phone, signed in as the same user, sees the same history. That is the main reason to identify signed-in customers rather than leaving them anonymous.
Security and privacy
Section titled “Security and privacy”A common and important question: can someone see another person’s past conversations just by typing their email into the chat? No. Here is how the widget protects conversation history.
History is tied to a token, not an email
Section titled “History is tied to a token, not an email”Each visitor’s browser holds an opaque visitor token in a first-party cookie. That token, and nothing else, is what grants access to a conversation. There is no way to look up conversations by email address. A visitor only ever holds a token for conversations their own browser started.
Typing an email proves nothing
Section titled “Typing an email proves nothing”Details a visitor types into the chat, whether through the pre-chat form or a Zev.set call, are treated as unverified. They are stored for display only. They never link the visitor to a contact and never unlock anyone’s history. So a person on another device who types someone else’s email sees only their own conversations, which for a new browser is none.
Cross-device history requires a verified identity
Section titled “Cross-device history requires a verified identity”The only thing that links conversations across devices is a verified Zev.identify call. The server recomputes the signature from your identify secret and compares it in constant time. A match is required before any contact link is made, and in production an identify call with a missing or wrong signature is rejected outright. Because the secret lives only on your backend, no one can forge a signature for another person’s id, so no one can reach another person’s history.
Defense in depth
Section titled “Defense in depth”- Embed-domain allowlist. Every request is checked against your brand’s allowlisted domains, so a leaked embed key used from another site returns nothing.
- Workspace and brand scoping. A visitor token is only honored for the workspace and brand it belongs to.
- No enumeration. Unknown tokens and unknown conversation ids return an empty result rather than an error, so they cannot be probed to discover what exists.
Shared devices
Section titled “Shared devices”The one thing to keep in mind is shared devices. Because anonymous history rides on the browser cookie, a second person using the same browser profile can see the previous person’s anonymous conversations, the same as with any cookie-based chat. This is not an email or identity weakness. It is the nature of an anonymous session on a shared browser. Verifying a customer with Zev.identify does not change it either way.
Choosing between set and identify
Section titled “Choosing between set and identify”| Use case | Method |
|---|---|
| A signed-in customer you can verify server-side | Zev.identify |
| Casual context, such as the current page or plan | Zev.set |
| An anonymous visitor filling in the chat form | Neither, the widget collects it |
Related
Section titled “Related”- JavaScript API for the full
window.Zevreference. - Customizing the widget to set how the widget asks anonymous visitors for their details.