MIDNIGHT/ docs
PricingSign inStart free

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.

node.js
const crypto = require("crypto");

const userHash = crypto
  .createHmac("sha256", process.env.MIDNIGHT_IDENTITY_SECRET)
  .update(user.id)
  .digest("hex");
python
import hmac, hashlib, os

user_hash = hmac.new(
    os.environ["MIDNIGHT_IDENTITY_SECRET"].encode(),
    user.id.encode(),
    hashlib.sha256,
).hexdigest()
php
$userHash = hash_hmac('sha256', $user->id, getenv('MIDNIGHT_IDENTITY_SECRET'));

3. Identify the visitor in the browser

javascript
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:

javascript
midnightchat("on", "identify_rejected", () => {
  console.warn("identity was refused — check the secret and the signed value");
});
A rejection does not tell you why
A wrong signature and a workspace with no secret configured both return exactly the same error. That is deliberate: distinguishing them would tell an attacker whether verification is switched on at all. When debugging, check both — and confirm you signed the id you actually sent, not the email.

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.

API reference
The identify endpoint and everything else.
Sources & training
What the agent knows and how to change it.