Signed customer identity
Prove who a logged-in visitor is with an HMAC-signed token generated on your own server.
A widget can run for both anonymous visitors and visitors you already know are logged in. To let Hark trust a visitor's identity (their name, email, plan, and any custom attributes), your own backend signs a token with a secret that only your server and Hark know. Hark verifies that signature before it trusts a single claim in it; nothing about identity is ever taken from the browser at face value.
Get your signing secret
Each widget has its own signing secret, shown on the widget's Install tab once it has been set. Contact your workspace owner if a widget does not have one configured yet; identity cannot be verified without it. Keep this secret on your server only. It must never appear in browser-side code, a mobile app bundle, or any place a visitor could read it.
Token format
An identity token is two base64url segments joined by a dot:
token = base64url(JSON.stringify(claims)) + "." + base64url(HMAC_SHA256(secret, payload))The first segment is the JSON claims, base64url-encoded. The second is an HMAC-SHA256 of that first segment, keyed with the widget's secret, also base64url-encoded. Hark recomputes the HMAC over the received payload and compares it to the presented one with a timing-safe check, so a token cannot be forged without the secret and cannot be tampered with after signing (changing any claim invalidates the signature).
Claims
| Field | Type | Notes |
|---|---|---|
userId | string, required | Your stable id for this customer. This is the only required claim. |
email | string, optional | Up to 320 characters. |
name | string, optional | Up to 200 characters. |
firstName | string, optional | Up to 100 characters. Falls back to the first word of name if omitted. |
plan | string, optional | Up to 100 characters, for example "pro". |
language | string, optional | Up to 35 characters, for example "en". |
attributes | object, optional | Up to 20 string key/value pairs; keys up to 64 characters, values up to 500 characters. Anything beyond the limit is dropped, not rejected. |
exp | number, optional | A Unix millisecond timestamp. If present and in the past, the token is rejected. |
Every field beyond userId is optional; omit anything you do not have. Oversized strings are truncated rather than causing a verification failure, but keep tokens well under 4 KB, the maximum accepted length.
Generate a token on your server
Sign the token wherever you already know the logged-in user, for example in the request handler that renders the page or in an endpoint your frontend calls right after login. This example uses Node's built-in crypto:
const { createHmac } = require('crypto')
function signHarkIdentity(secret, claims) {
const payload = Buffer.from(JSON.stringify(claims)).toString('base64url')
const mac = createHmac('sha256', secret).update(payload).digest('base64url')
return `${payload}.${mac}`
}
const token = signHarkIdentity(process.env.HARK_WIDGET_SECRET, {
userId: user.id,
email: user.email,
name: user.fullName,
plan: user.billingPlan,
attributes: { company: user.companyName },
})Hand the resulting string to the browser (as page data, an API response, or similar) and pass it to the widget:
window.Hark.identify({ customerToken: token })Expiry and re-signing
If you set exp, mint a fresh token whenever you call identify rather than caching one for the whole session; a token you generate on each authenticated page load already refreshes it naturally. There is no revocation list, since a signed token is only ever trusted at the moment it is presented and expiry is the mechanism for bounding how long a leaked token stays useful.
Page context is not identity
window.Hark.setContext(...) sends unsigned, client-supplied data (current page, plan, product area, locale, a few custom attributes). Hark stores a bounded, known-key subset of it and uses it only as enrichment, for example to personalize a greeting or hint a routing rule. Because it carries no signature, it can never establish who a visitor is; only a verified identity token does that. If a claim appears in both a signed identity token and unsigned page context, the signed value wins.