Identity
Read state and targeting depend on knowing who the user is. The browser can’t be trusted to say that itself, so rollout accepts three kinds of identity. You choose under Settings → Identity.
| Mode | How | Targeting |
|---|---|---|
| Signed token | Your server signs a JWT with the project’s signing secret. | Yes |
| Your auth provider | A JWT from Clerk, Auth0, Supabase Auth, … checked via JWKS. | Yes |
| Unverified (opt-in) | The SDK sends a plain user id. | No |
The SDK sends tokens as Authorization: Bearer <token> along with the publishable key in
X-Rollout-Key.
Signed tokens (HS256)
Section titled “Signed tokens (HS256)”Generate a signing secret under Settings → Identity (it starts with rollout_sig_). On your
server, sign a short-lived HS256 JWT for the signed-in user.
| Claim | Required | Description |
|---|---|---|
sub |
Yes | Your user id, any string up to 256 characters. |
exp |
Yes | Expiry. Keep it short, e.g. one hour; the SDK fetches a new token after a 401. |
org |
No | The organization or account the user belongs to. |
created_at |
No | When the user signed up, as Unix seconds or an ISO date. Older announcements start out read. |
attrs |
No | An object of custom attributes for targeting, e.g. { "plan": "pro" }. |
import { SignJWT } from 'jose'
const secret = new TextEncoder().encode(process.env.ROLLOUT_SIGNING_SECRET)
export function rolloutToken(user) { return new SignJWT({ org: user.orgId, created_at: Math.floor(user.createdAt.getTime() / 1000), attrs: { plan: user.plan, role: user.role }, }) .setProtectedHeader({ alg: 'HS256' }) .setSubject(user.id) .setExpirationTime('1h') .sign(secret)}import jwt from 'jsonwebtoken'
export function rolloutToken(user) { return jwt.sign( { sub: user.id, org: user.orgId, created_at: Math.floor(user.createdAt.getTime() / 1000), attrs: { plan: user.plan }, }, process.env.ROLLOUT_SIGNING_SECRET, { algorithm: 'HS256', expiresIn: '1h' }, )}Serve it from an endpoint your frontend can call, and pass a function to the SDK so it can fetch fresh tokens:
feed.token = () => fetch('/api/rollout-token').then((r) => r.text())Rotating the secret
Section titled “Rotating the secret”A project can have several active secrets; rollout accepts a token signed with any of them. Generate a new secret, deploy it to your servers, then retire the old one under Settings → Identity. Tokens signed with a retired secret are rejected.
Your auth provider (JWKS)
Section titled “Your auth provider (JWKS)”If your users already have a JWT from an auth provider, rollout can verify it against the provider’s public keys instead. Under Settings → Identity, set:
- JWKS URL: the provider’s key set, e.g.
https://your-app.clerk.accounts.dev/.well-known/jwks.json. - Audience (
aud): required. Tokens must be issued for this audience, and must haveexp. - Claim mapping: which claims hold the user id (
subby default) and organization (org).
When a JWKS URL is set, signing secrets are not used.
Unverified users
Section titled “Unverified users”If you allow it under Settings → Identity (“Also allow unverified users”), the SDK can pass a plain identity instead of a token:
feed.user = { id: 'user_123', org: 'org_42', createdAt: '2025-01-10T00:00:00Z' }The SDK sends it base64url-encoded in the X-Rollout-User header. Anyone can claim any id this
way, so read state can be spoofed, and targeted announcements are never shown to unverified
users. Use it for prototypes or public, logged-out pages only.
Errors
Section titled “Errors”| Code | Meaning |
|---|---|
identity_required |
No token and no unverified user was sent. |
invalid_token |
Bad signature, wrong audience, missing sub/exp, or expired. |
verified_identity_required |
An unverified user was sent, but the project requires tokens. |
origin_not_allowed |
The page’s origin isn’t in Settings → API keys → Allowed origins. |