Get started
Identify your users
Tell Meles who is using your app, so a campaign can target them by plan or role and their answers are linked to them. Sign each user ID on your server, so nobody can pretend to be someone else.
Call identify
Once your user has signed in, pass their ID and the traits you want to target on. The third argument carries the hash your server computed for that ID:
Meles.identify('user_8412', { plan: 'pro', role: 'admin' }, { userHash: hashFromYourServer });- The user ID is yours, up to 256 characters. Meles links the visitor’s earlier, anonymous activity to it.
- Traits are strings (up to 1,000 characters), numbers, booleans or
null, under names of up to 128 characters that don’t start with$. Send at most 50 per person, with their company’s included. Later calls add to them and overwrite them. - A company goes in the third argument too:
{ company: { id: 'acme', traits: { plan: 'enterprise' } }, userHash }. - When your user signs out, call
Meles.reset(). It forgets them and starts a new anonymous ID, so the next person on a shared device isn’t counted as them. Their frequency caps stay with the device.
Sign the user ID on your server
Every site has an identity secret: Settings → Sites, your site, Identity secret. With it, Meles refuses an identify whose userHash is missing or wrong, with 401 ingest.identity_unverified, and stores nothing. So someone who edits your page can’t claim to be another user.
The hash is HMAC-SHA256 of the user ID’s UTF-8 bytes, keyed with the secret’s UTF-8 bytes exactly as issued, written as 64 lower-case hex characters. Compute it on your server and put it in the page with the user ID. The secret never goes in the page. Don’t normalise or trim the user ID: hash exactly the string you pass to identify. The hash is a bearer credential: it never expires, so anyone who has a user’s ID and hash can identify as that user, from any page or script, until you rotate the secret. Treat it like a session token: put it only in that signed-in user’s own page, over HTTPS, and keep it out of URLs, logs, analytics and any cache shared between users.
Node
import { createHmac } from 'node:crypto';
/**
* The `userHash` that `Meles.identify()` takes for `userId`: HMAC-SHA256 keyed with the site's
* identity secret, over the user ID's UTF-8 bytes, as 64 lower-case hex characters. Run it on your
* server and send the result to the page: the secret never reaches the browser.
*/
export function userHash(userId, identitySecret) {
return createHmac('sha256', identitySecret).update(userId, 'utf8').digest('hex');
}Python
import hashlib
import hmac
def user_hash(user_id: str, identity_secret: str) -> str:
"""The userHash that Meles.identify() takes for user_id.
HMAC-SHA256 keyed with the site's identity secret, over the user ID's UTF-8 bytes, as 64
lower-case hex characters. Run it on your server and send the result to the page: the
secret never reaches the browser.
"""
return hmac.new(identity_secret.encode("utf-8"), user_id.encode("utf-8"), hashlib.sha256).hexdigest()C#
using System.Security.Cryptography;
using System.Text;
/// <summary>Identity verification for Meles: copy this class to your server.</summary>
internal static class MelesIdentity
{
/// <summary>
/// The <c>userHash</c> that <c>Meles.identify()</c> takes for <paramref name="userId"/>:
/// HMAC-SHA256 keyed with the site's identity secret, over the user ID's UTF-8 bytes, as 64
/// lower-case hex characters. The secret never reaches the browser.
/// </summary>
public static string UserHash(string userId, string identitySecret) =>
Convert.ToHexStringLower(HMACSHA256.HashData(Encoding.UTF8.GetBytes(identitySecret), Encoding.UTF8.GetBytes(userId)));
}Test vectors
Any language’s HMAC-SHA256 will do. These are the vectors the samples above are checked against on every change to them; the secrets are examples, never real. All four are in identify-hmac.json.
| Vector | Secret | User ID | userHash |
|---|---|---|---|
| ascii | idsec_ExampleSecretForTheHmacVectors0001 | user_8412 | 64e23579487d20128d12d6fe6bd9080de42d0ee822e570feae945c128173dbdd |
| non-ascii | idsec_ExampleSecretForTheHmacVectors0001 | Zoë Ångström 🦡 | b66572a427d0aacde76987aecd22238d7c3a3b8f0e2d2dc937615dc8319ef559 |
| decomposed | idsec_ExampleSecretForTheHmacVectors0001 | Zoë Ångström 🦡 | 5b198610e962a757dd473854cfa26aadc2fd1a6f507125b0e7bda8d760e5aaea |
The second and third differ only in how the accented letters are encoded, as one character or a letter and an accent, which is why the user ID is hashed exactly as given.
Rotating the secret
Rotate secret on the same card issues a new secret, shown once. For the next 24 hours Meles accepts a hash made with either, so deploy the new one to your servers within that day. Rotating is also how you end a hash that has leaked: it stops working when the 24 hours end, or at once if you rotate a second time, which also refuses any of your servers still hashing with the old secret.
Identifying from your server
A server can identify a person itself, with a secret API key that holds the runtime scope, through the identify_user tool or POST /v1/identify, naming the site. The key proves who is calling, so no userHash is needed. See Connect an AI agent for keys.