Documentation Index
Fetch the complete documentation index at: https://docs.calmtreasury.xyz/llms.txt
Use this file to discover all available pages before exploring further.
Enable identity tokens in Privy
Calm authenticates users via Privy’s identity tokens — short-lived JWTs that carry the user’ssub, linked wallet, and linked email. They are not
the same as Privy access tokens. You need to enable identity tokens once
per Privy app before the SDK can bootstrap a session.
Open the Privy dashboard
Sign in at dashboard.privy.io and pick
your app.
Toggle "Return user data in an identity token" ON
Scroll the Advanced tab until you see it. Once enabled, the user’s

email and linked_accounts (including the linked wallet) ride inside
the identity token — which is exactly what the Calm SDK forwards to
POST /v1/session.
The chain of trust
What POST /v1/session does
Reads the Privy identity token
From
Authorization: Bearer <jwt>. The SDK calls getAccessToken() (or
Privy’s getIdentityToken() in practice).Verifies the signature
Against
https://auth.privy.io/api/v1/apps/<aud>/jwks.json. JWKS is
cached per app id for the process lifetime.Extracts the linked wallet
The first entry in
linked_accounts with type: "wallet". The wallet
address is lower-cased before binding.Cookie attributes
| Attribute | Value | Why |
|---|---|---|
HttpOnly | ✓ | JavaScript in the partner app can’t read it. |
Secure | ✓ | Required by browsers when SameSite=None. |
SameSite=None | ✓ | Required to attach on cross-site fetches (partner → Calm). |
Partitioned | ✓ | Avoids being categorized as a third-party tracking cookie in Safari/Chrome (CHIPS). |
Max-Age | 3600 | One hour. SDK re-bootstraps when expired. |
What’s in the cookie
What’s NOT in the cookie
- Email (collected in the register form, stored server-side)
- First/last name (passed to Bridge at register, not echoed back)
- KYC state (proxied fresh from Bridge on every state read)
- Virtual account details (proxied fresh from Bridge)
CORS
The browser must include the cookie on cross-site fetches. That requires:- The API responds with
Access-Control-Allow-Origin: <partner-origin>(not*) - The API responds with
Access-Control-Allow-Credentials: true - The SDK sets
credentials: "include"(we already do)
