When to turn it on
- You run Sendora on an authenticated product (dashboards, apps, logged-in surfaces).
- You're sending sensitive traits — plan, MRR, email — that you don't want a stranger overwriting.
- You intend to build audiences or journeys keyed on user ID. A poisoned profile is a poisoned campaign.
Public, unauthenticated marketing pages are fine without it.
1. Rotate your identity secret
In Dashboard → Settings → Identity, click Rotate secret. You'll see a sis_… value exactly once. Store it in your backend's secret manager — it is never shown again.
SENDORA_IDENTITY_SECRET=sis_xxxxxxxx...2. Sign a token in your backend
When a user loads your page, compute HMAC-SHA256(secret, userId), hex-encode it, and prefix with hmac_v1:. Ship it to the browser alongside the authenticated page response.
import { createHmac } from class="tk-s">"node:crypto";
export function signIdentity(userId: string) {
const secret = process.env.SENDORA_IDENTITY_SECRET!;
const mac = createHmac(class="tk-s">"sha256", secret).update(userId).digest(class="tk-s">"hex");
return class="tk-s">`hmac_v1:${mac}`;
}3. Pass the token to the SDK
Call identify() with the token as the third argument.
import { Sendora } from class="tk-s">"@sendoracloud/sdk-web";
const s = new Sendora({ publicKey: class="tk-s">"pk_live_..." });
s.identify(
class="tk-s">"u_123",
{ email: class="tk-s">"sam@acme.co", plan: class="tk-s">"startup" },
{ identityToken: window.__SENDORA_IDENTITY_TOKEN__ }
);4. Flip the strict toggle
Back in Settings → Identity, enable Strict identity. Every event that arrives without a valid token is now rejected:
- Single-event
POST /events—401withIDENTITY_UNVERIFIED. - Batch
POST /events/batch— the failing events are dropped from the batch; valid ones still land. The response returns arejectedarray with the index and reason for each skipped event.
How verification works
- Only the
hmac_v1:scheme is accepted today. Future schemes will bump the prefix. - The backend caches your secret in-memory for 60 seconds after first use. Rotating the secret propagates within that window.
- Comparison is constant-time. Tokens are never logged.
Rotating without downtime
- Deploy backend that signs with the new secret.
- Rotate the secret in the dashboard.
- Within 60 s, the backend cache drops the old value and starts verifying the new one.
To avoid any gap, keep strict mode off for one deploy cycle, then re-enable it after the rotation settles.
Troubleshooting
- Every event rejected. Confirm the signed string is the exact
userIdyou pass toidentify()— no trimming, no prefix. - Tokens accepted in dev, rejected in prod. You rotated the secret in the dashboard but the backend still signs with the old one. Update your env var.
- Works for single events, fails for batch. Batch endpoints drop failing events silently — check the
rejectedarray in the response.