Identity verification
Anyone can type “I'm alice@example.com” into a chat box. Identity verification lets your server vouch for who the visitor is, so the agent can safely answer questions about their account instead of treating every claim as unproven.
How it works
Your workspace has a secret. Your server signs the user's id with it, and the browser sends the id and signature together. We recompute the signature; if it matches, the identity is trusted, because only someone holding the secret could have produced it.
The secret never reaches the browser. That is the entire security property, and everything below is in service of it.
1. Get your secret
The Deploy tab shows it, in the form mcid_ followed by 64 hex characters. Only an owner or admin can read or rotate it, because whoever holds it can assert any identity. Store it as a server-side environment variable.
2. Sign the user id on your server
Compute an HMAC-SHA256 of the user id, keyed with your secret, and render the hex digest into the page. The message is the user id alone — no email, no timestamp, no JSON.
const crypto = require("crypto");
const userHash = crypto
.createHmac("sha256", process.env.MIDNIGHT_IDENTITY_SECRET)
.update(user.id)
.digest("hex");import hmac, hashlib, os
user_hash = hmac.new(
os.environ["MIDNIGHT_IDENTITY_SECRET"].encode(),
user.id.encode(),
hashlib.sha256,
).hexdigest()$userHash = hash_hmac('sha256', $user->id, getenv('MIDNIGHT_IDENTITY_SECRET'));3. Identify the visitor in the browser
midnightchat("identify", {
user_id: "usr_8134", // the same id you signed
user_hash: userHash, // from your server, never computed here
email: "alice@example.com", // optional traits
name: "Alice Chen",
plan: "growth",
});email, name, and phone are stored as fields on the contact. Every other key is kept as a custom field. The contact is matched on your user id, so identifying the same person again updates their record rather than creating a second one.
external_id and hmac work as aliases for user_id and user_hash if you prefer them. If either the id or the signature is missing, the call is dropped in the browser without a network request.
What you get back
Subscribe to identify_verified and identify_rejected to see the outcome:
midnightchat("on", "identify_rejected", () => {
console.warn("identity was refused — check the secret and the signed value");
});Security properties, stated plainly
- The signature does not expire. Only the user id is signed, so a hash that leaks stays valid for that user indefinitely. Treat it as a credential: render it into authenticated pages only, and never log it.
- Rotation invalidates everything. Rotating the secret breaks every hash you have already issued. Deploy the new secret to your servers first, then rotate, or identify calls will fail in between.
- Sign an opaque id. Prefer your internal user id over an email address. Emails change hands, ids do not, and a signed email is a signed claim you may not want to be permanent.
Testing it
Compute a hash for a test id on your machine with the same secret, call identify with it, and watch for identify_verified. If you get a rejection, print both the id you signed and the id you sent and compare them character by character — a trailing newline from a shell command is the usual culprit.