message.comDevelopers

Identify logged-in users with HMAC

Unsigned Message.identify calls are trusted from the browser. That means anyone could open the console and claim to be your customer. For production, sign the identify payload with an HMAC user-hash. The widget refuses to accept a different identity once a valid userHash is present.

Read the Identifying visitors reference for the full protocol. This tutorial is the practical implementation.

1. Get your signing secret

  1. Open app.message.com.
  2. Go to Settings → Security → Identity verification.
  3. Copy the Widget signing secret. Treat it like a password. Server-side only.
  4. Store it in your environment as MESSAGE_WIDGET_SIGNING_SECRET. Never commit it.

2. Compute the user-hash server-side

The user-hash is HMAC-SHA256(secret, userId) output as lowercase hex. Use whatever language your backend speaks.

Node.js

userHash.ts
// server: compute the userHash
import { createHmac } from "node:crypto";

const SECRET = process.env.MESSAGE_WIDGET_SIGNING_SECRET; // workspace secret

export function userHashFor(userId: string): string {
  return createHmac("sha256", SECRET).update(userId).digest("hex");
}

Python

user_hash.py
# server: compute the userHash
import hmac, hashlib, os

SECRET = os.environ["MESSAGE_WIDGET_SIGNING_SECRET"].encode()

def user_hash_for(user_id: str) -> str:
    return hmac.new(SECRET, user_id.encode(), hashlib.sha256).hexdigest()

Ruby

user_hash.rb
# server: compute the userHash
require "openssl"

SECRET = ENV.fetch("MESSAGE_WIDGET_SIGNING_SECRET")

def user_hash_for(user_id)
  OpenSSL::HMAC.hexdigest("SHA256", SECRET, user_id.to_s)
end

PHP

user_hash.php
<?php
// server: compute the userHash
$secret = getenv("MESSAGE_WIDGET_SIGNING_SECRET");
function user_hash_for(string $userId): string {
  global $secret;
  return hash_hmac("sha256", $userId, $secret);
}

3. Pass userHash to the client

Two patterns. Both work.

  • Server-rendered HTML. Inject the hash inline when you render the page. The hash is bound to the signed-in user; rendering it server-side is safe.
  • Authenticated endpoint. The browser calls GET /api/me/message-identity after sign-in and gets back the identify payload. Use this for SPAs.

4. Call Message.identify with userHash

Once the widget bundle has loaded, call Message.identify with the signed payload.

HTML
<script>
  window.MessageOnReady = function () {
    window.Message.identify({
      email: "[email protected]",
      name: "Alice Johnson",
      userId: "crm-4567",
      userHash: "<COMPUTED_ON_SERVER>",     // hex string from server
      plan: "pro",
      mrr: 99,
      createdAt: 1714000000                  // unix seconds
    });
  };
</script>

From here on, agents see this visitor as Alice Johnson, with her CRM ID, plan, and MRR attached. No one can spoof a different identity from the browser without the workspace secret.

5. Re-identify on logout

When the user signs out, call Message.signout(). This clears the local conversation state and detaches the identified record. The next chat is anonymous until someone signs in again.

Common pitfalls

  • Hashing email instead of userId. Use a stable internal ID (UUID, CRM ID). Email can change. If you must hash the email, do it consistently and remember the hash is bound to whatever string you signed.
  • Hashing client-side. If your signing code runs in the browser, the secret is exposed. Anyone can mint hashes. The whole protocol falls apart. Always compute on the server.
  • Secret leakage. Rotate the secret immediately in Settings → Security → Identity verification if it leaks. Existing hashes are invalidated.
  • Multiple workspaces. Each workspace has its own signing secret. If you operate two workspaces (staging and production), use separate environment variables and separate secrets.
  • Stale hash on identity change. If you change the user's userId upstream, you must compute and ship a new hash. Old hashes will not validate against the new ID.

Next steps