Identifying visitors Live
By default every visitor is anonymous. Identifying them links chats across sessions, surfaces their CRM data inside the agent inbox, and lets message AI ground responses in their actual history. For production use, sign the payload with HMAC so no one else can impersonate them.
Basic identification
Call Message.identify after the bundle loads. Pass any combination of reserved keys plus arbitrary custom attributes.
Message.identify({
email: '[email protected]',
name: 'Alice Johnson',
userId: 'crm-4567',
plan: 'pro',
mrr: 99
});Custom attributes (here plan and mrr) appear in the visitor sidebar in the inbox and are searchable. Numbers, strings, and booleans are supported. Nested objects are stored but not searchable.
Anyone with browser dev tools can call Message.identify({ email: '[email protected]' }). For production, always sign the payload with HMAC (next section). Without signing, the inbox shows an “unverified identity” warning next to the visitor.
HMAC identity verification
To prove a visitor is who they claim to be, compute a server-side HMAC-SHA256 of the user's stable identifier (their userId) using your workspace's signing secret. Render the hash into the page, then pass it as userHash to identify.
Message.identify({
email: '[email protected]',
userId: 'crm-4567',
userHash: '<server-side HMAC-SHA256>'
});Get your signing secret from Settings → Security → Identity verification in the dashboard. Each workspace has one secret, rotatable on demand. Once at least one verified visitor has shipped with a given secret, rotating invalidates older signed hashes (recompute them on rotation).
Compute the hash on your server
The hash function is plain HMAC-SHA256 over the UTF-8 bytes of userId with the workspace signing secret as the key. Output is lowercase hex.
import { createHmac } from 'crypto';
const secret = process.env.MESSAGE_WIDGET_SIGNING_SECRET;
const userHash = createHmac('sha256', secret)
.update(userId)
.digest('hex');
// Render userHash into your HTML or expose it via API.
res.json({ userHash });import hmac, hashlib, os
secret = os.environ["MESSAGE_WIDGET_SIGNING_SECRET"].encode()
user_hash = hmac.new(secret, user_id.encode(), hashlib.sha256).hexdigest()require "openssl"
secret = ENV.fetch("MESSAGE_WIDGET_SIGNING_SECRET")
user_hash = OpenSSL::HMAC.hexdigest("SHA256", secret, user_id)<?php
$secret = getenv("MESSAGE_WIDGET_SIGNING_SECRET");
$user_hash = hash_hmac("sha256", $user_id, $secret);Read the RFC 2104 HMAC specification if you need to verify the algorithm details.
Rendering the hash
Inject the hash into the page on each request. Do not cache pages with embedded user hashes across users.
<!-- Server-rendered template -->
<script>
window.MessageOnReady = function () {
Message.identify({
email: "{{ user.email }}",
userId: "{{ user.id }}",
userHash: "{{ user_hash }}"
});
};
</script>Merging anonymous and identified records
When a visitor browses anonymously, sends a message, then logs in and triggers an identify call, the anonymous record merges into the identified one. The conversation history transfers, custom events stay attached, and the dashboard view shows a single timeline.
Conflicts (two existing identified records with the same email) prefer the most recently active record. Merging is logged in the workspace audit log. See Audit log.
Signing out
When the user logs out of your app, call Message.signout(). This clears the local conversation state so the next user on the same browser starts fresh.
// On logout from your app
function logout() {
Message.signout();
// ... rest of your logout
}Without an explicit signout, the next visitor inherits the previous session's identity. Always pair logins and logouts.
Reserved keys
email, string. Primary lookup key.userId, string. Stable identifier in your system. Required for HMAC verification.userHash, string. HMAC-SHA256 hex ofuserIdwith your workspace secret.name, string. Display name in the inbox.phone, string in E.164 format.createdAt, integer. Unix seconds.locale, string. BCP 47 code.
Anything else is treated as a custom attribute. See Custom attributes.
Common pitfalls
- Hashing the wrong field. Hash
userId, not email. Email can change (a customer rotates addresses) and would break the signature. - Leaking the secret. The signing secret never appears in the browser. If you accidentally embed it in client code, rotate it immediately from
Settings → Security. - Caching pages with embedded hashes. User-specific hashes must not appear in static or CDN-cached HTML. Set
Cache-Control: private, no-storeon those pages. - Forgetting signout. Without
Message.signout()on logout, the next visitor sees the previous user's history. Pair every login with a logout call.