Skip to main content
bankroll.identity() resolves a signed identity token — a JWT scoped to your app. Use it to know who the user is: their stable id, their Bankroll handle, region, and verified age. Send it to your server and verify it there before trusting anything in it.
import { bankroll } from "@joinbankroll/sdk";

const token = await bankroll.identity();
The first call for a new app shows the user exactly what they’re sharing; once granted, later calls resolve without re-prompting. Tokens are short-lived (~15 minutes) — the SDK caches the current token and mints a fresh one before it expires, and concurrent calls share one mint, so call identity() as often as you like. The promise rejects with a BankrollErrorconsent_declined if the user declines the consent prompt, unavailable / update_required outside a current Bankroll host.

Send it to your server

The token travels in the x-bankroll-token header. Decorate fetch once and every request carries it:
import { withBankrollToken } from "@joinbankroll/sdk";

const appFetch = withBankrollToken(fetch);
await appFetch("/api/session", { method: "POST" });
In a plain browser (status 'unavailable') requests go out bare and your server responds 401. Any other failure — including a consent decline — propagates; it is never silently downgraded to a bare request.

Verify it on your server

Never trust an identity token that hasn’t been verified. In Node, verifyToken is the entire route guard:
import { verifyToken } from "@joinbankroll/sdk/server";
import { BANKROLL_TOKEN_HEADER } from "@joinbankroll/sdk";

const verified = await verifyToken(req.headers.get(BANKROLL_TOKEN_HEADER), {
  audience: "https://acme.example", // your app's exact origin
});
if (!verified) {
  return Response.json({ error: "unauthorized" }, { status: 401 });
}
// verified.sub is the user's stable id — bind it to your session.
It checks the RS256 signature against Bankroll’s public keys, the issuer, and — critically — that the token was minted for your origin. It accepts null/undefined and returns null on any failure, so one if (!verified) covers missing, forged, expired, and wrong-app tokens alike. On other stacks, verify the JWT directly:
WhatValue
AlgorithmRS256
Public keys (JWKS)https://joinbankroll.com/.well-known/jwks.json
Issuer (iss)https://joinbankroll.com
Audience (aud)Your app’s origin (e.g. https://acme.example)
Always check aud equals your own origin, byte-for-byte — https, lowercase host, no default port, no trailing slash. The token is scoped to your app, so a token minted for a different app must not sign a user into yours. Also fetch the keys from the JWKS URL and cache them — never hard-code a key.
See the Quickstart for full verification snippets in Node.js, Ruby, and Python.

Claims

A verified token gives you:
// what verifyToken returns
interface VerifiedToken {
  sub: string;         // stable user id — bind to your session, store as-is
  username?: string;   // Bankroll handle; omitted when unset
  geo?: string;        // 'US-NY' (ISO 3166-2) or 'US'; omitted when unresolvable
  identity?: { age?: number } | false; // verification state — see below
  aud: string; iss: string; iat: number; exp: number;
}
ClaimAlways presentMeaning
subYesThe user’s stable id. Use it as the primary key for the user on your platform.
issYesAlways https://joinbankroll.com.
audYesYour app’s origin.
iat / expYesIssued-at and expiry. Tokens are short-lived (~15 minutes); the SDK re-mints automatically.
usernameNoThe user’s Bankroll handle. Omitted if they haven’t set one.
geoNoThe user’s region as an ISO 3166-2 code (e.g. US-NY), or a bare country code (US) when only the country is known. Omitted if unresolvable.
identityNoIdentity-verification result — three states. Absent: the user has never been through verification. false: verification was rejected. Object: verified — { "age": <number> }, with age omitted if their date of birth isn’t on file.
Wire name: in the raw JWT this claim is delivered as kyc. The SDK’s verifyToken reads either name and always exposes it as identity; if you verify raw JWTs on another stack, read identity ?? kyc.
Optional claims are omitted, never set to null — test for presence.

Gating eligibility

For real-money play, compute eligibility on your server from the verified token — never from an unverified client value:
  • typeof verified.identity === "object" — the user has cleared identity verification. Don’t gate on mere presence: a rejected user’s value is false, which is present too. Absent means they haven’t verified yet.
  • verified.identity.age — the user’s verified age, for age-restricted play.
  • verified.geo — the user’s region, for where-you-can-play rules.
const verified = await verifyToken(token, { audience: MY_ORIGIN });
const canPlayRealMoney =
  verified !== null &&
  typeof verified.identity === "object" &&
  (verified.identity.age ?? 0) >= 21 &&
  ALLOWED_REGIONS.has(verified.geo ?? "");

Handling a decline

A user declining the consent prompt is a normal choice, not an error. identity() rejects with a BankrollError whose code is consent_declined — catch it and show your own “connect to continue” state rather than surfacing an error.
import { bankroll, BankrollError } from "@joinbankroll/sdk";

try {
  const token = await bankroll.identity();
  // ...verify server-side, sign the user in
} catch (e) {
  if (e instanceof BankrollError && e.code === "consent_declined") {
    showConnectPrompt(); // let them try again
    return;
  }
  throw e;
}