Bind a signed-in visitor to your login or customer id with a short-lived token your server signs with a Pixel server key.
Browser events cannot claim a person, because the public key is visible in every page and any script could use it. To bind a signed-in visitor to your subject_ref (your login id) or commerce_subject_ref (your customer id), your server mints a short-lived identity token with one of the property's server keys, and the page passes it to kentroPixel('identify', token).
In this page
- Token format
- Mint a token
- Hand it to the page
- What the collector does
Token format
token = payload + "." + signature
payload = base64url(UTF-8 JSON { "sub", "typ", "exp", "kid" }) (no padding)
signature = base64url(HMAC-SHA256(serverKeySecret, payload)) (no padding)
| Claim | Value |
|---|---|
sub | The pseudonymous reference: 1 to 256 characters, no whitespace, no @ (never an email address). |
typ | subject_ref or commerce_subject_ref: the reference the collector sets. |
exp | Expiry in Unix seconds, at most 24 hours after minting. |
kid | The sk_ key id of an active server key of the same property as the browser's public key. |
The HMAC key is the server key secret exactly as it was shown (its UTF-8 bytes, not base64-decoded), and the signature covers the base64url payload string. A token is at most 1,024 characters. Create server keys as described in Server keys.
Mint a token
const crypto = require('node:crypto');
const KEY_ID = process.env.KENTRO_PIXEL_KEY_ID; // sk_...
const SECRET = process.env.KENTRO_PIXEL_KEY_SECRET; // shown once when the key was created
function identityToken(sub, typ = 'subject_ref', ttlSeconds = 3600) {
const claims = { sub, typ, exp: Math.floor(Date.now() / 1000) + ttlSeconds, kid: KEY_ID };
const payload = Buffer.from(JSON.stringify(claims), 'utf8').toString('base64url');
const signature = crypto.createHmac('sha256', SECRET).update(payload).digest('base64url');
return `${payload}.${signature}`;
}
console.log(identityToken('usr_8f3a2c91'));
import base64
import hashlib
import hmac
import json
import os
import time
KEY_ID = os.environ["KENTRO_PIXEL_KEY_ID"] # sk_...
SECRET = os.environ["KENTRO_PIXEL_KEY_SECRET"] # shown once when the key was created
def b64url(data):
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
def identity_token(sub, typ="subject_ref", ttl_seconds=3600):
claims = {"sub": sub, "typ": typ, "exp": int(time.time()) + ttl_seconds, "kid": KEY_ID}
payload = b64url(json.dumps(claims, separators=(",", ":")).encode("utf-8"))
signature = b64url(hmac.new(SECRET.encode("utf-8"), payload.encode("ascii"), hashlib.sha256).digest())
return payload + "." + signature
print(identity_token("usr_8f3a2c91"))
Hand it to the page
Render the token into the page the signed-in visitor loads, after the install snippet:
<script>
kentroPixel('identify', '<identity token minted by your server>');
</script>
The SDK keeps the token in memory only. Call identify on every page load after login, and kentroPixel('identify', null) on sign-out.
What the collector does
- It verifies each distinct token of a request once, at most 5 per request: the structure, a server key that exists, is active and belongs to the same property, the signature (compared in constant time) and the expiry (in the future, and no more than 24 hours ahead, with 5 minutes of tolerance for your server's clock).
- A valid token sets
subject_reforcommerce_subject_reftosubon the events that carry it; any other person reference the browser sent is dropped. - A missing or failing token never rejects an event: the person reference is dropped and the event is accepted anonymously with
reason_code: IDENTITY_UNVERIFIED. - Tokens are never stored. Revoking the server key invalidates every token it signed.
Never send raw ids from the browser
Do not put
subject_reforcommerce_subject_refin browser events, and never use an email address assub. Server events set person references directly; see Server API.

