plandalf.identify()
Set the current visitor identity.
Implemented by the browser object; parameter and return types follow the delegated class declaration.
Workspace snapshot: the inspected SDK has local changes. Source links identify its base commit and may differ from this working copy. Verify the SDK build you deploy before relying on unreleased behavior.
Call this after plandalf.ready(). The browser wrapper can return undefined before initialization.
Signature
plandalf.identify(token: string): voidArguments
| Name | Type | Meaning |
|---|---|---|
token | string | Server-signed customer JWT. Plain email strings are stored but do not identify an account on the backend. |
Example
plandalf.ready(async () => {
plandalf.identify(customerJwt)
await plandalf.present("pro-plan")
})Returns
void
Behaviour
Stores a server-signed customer JWT for later checkout requests and returns void. This call makes no validation request and does not prove identity acceptance or subscription ownership. The checkout server decodes the token when a session starts. Keep the signing secret on your server.
Sign a customer JWT
This guide describes the inspected workspace implementation. Check the SDK and server versions deployed to your integration before replaying it.
identify(token) stores a customer JWT and returns void. It makes no validation request. Plandalf decodes the JWT when checkout starts with POST /client/sessions, using Authorization: Bearer <token>. A successful call to identify() does not prove the server accepted the identity.
Sign on your server with the full secret value of an active organization API key, using HS256. Set the JWT header's kid to that key's encoded identifier (SQID) or full route key. A masked preview, key prefix, raw secret, or organization ID is not the key identifier. The key must belong to the checkout's organization. A client_id claim inside the payload does not select the key; the JWT header's kid does. Older OAuth client_id / client_secret credentials are not organization API keys.
Recommended header and payload (timestamps shown as examples; generate fresh values):
{
"alg": "HS256",
"typ": "JWT",
"kid": "YOUR_API_KEY_SQID"
}{
"sub": "usr_123",
"email": "jane@example.com",
"name": "Jane Example",
"iat": 1790726400,
"exp": 1790730000
}Never expose the signing secret in a script tag, public environment variable, or browser bundle. Return only the signed token from your authenticated server endpoint. Derive claims from the authenticated account and your own records.
import jwt from "jsonwebtoken";
export function plandalfJwt(user) {
return jwt.sign(
{
sub: String(user.id),
email: user.email,
name: user.name
},
process.env.PLANDALF_API_KEY,
{
header: { kid: process.env.PLANDALF_API_KEY_ID },
algorithm: "HS256",
expiresIn: "1h"
}
);
}PLANDALF_API_KEY is the complete secret key. PLANDALF_API_KEY_ID is the encoded identifier or full route key for that same key, not its preview.
JWT claims
Send identifiers and contact fields as strings. Use a stable, non-empty sub for your application's user ID.
| Claim | Purpose | Required? |
|---|---|---|
sub | Your application's user ID. Resolves or creates a customer by external_id, scoped to the organization, and records the checkout subject. | Recommended for account identity. |
email | Prefills checkout email and supplies contact details when creating a customer from sub. Email alone does not resolve an existing customer. | Optional. |
name | Prefills checkout name and supplies the customer's name. | Optional. |
customer | An existing payment gateway customer ID, such as Stripe's cus_.... Matches an existing customers.reference_id within the organization. This lookup does not verify unknown IDs against Stripe. Takes precedence over sub for customer lookup. | Optional. |
subscription | Can supply the backend checkout subscription target and default its intent to upgrade. It does not prove subscription ownership or paid access. Verify that separate journey before using it. | Optional. |
mode | Not read by the inspected customer-token decoder. It does not authorize SDK test mode; see the test checkout requirements below. | Optional. |
grp | Group identifier carried in JWT metadata. It does not select an organization or grant team permissions. | Optional. |
exp | Expiry as Unix seconds. Tokens are rejected at or after this timestamp. | Always include it; the current decoder does not require it. |
iat | Issued-at time as Unix seconds. A future value is rejected when nbf is absent. | Added by jsonwebtoken by default. |
nbf | Earliest valid time as Unix seconds. Tokens are rejected before this time. | Optional. |
Legacy aliases are accepted: customer_id, then id, fall back when customer is absent; subscription_id falls back when subscription is absent. Prefer customer for Stripe customer identity. The inspected SDK types do not declare a per-call subscription selector. Never put your application's user ID in id or customer; use sub.
When both customer and sub are present, customer lookup uses customer; it does not check that the two identifiers belong to the same account. If the gateway customer is not already present in Plandalf, that lookup does not fall back to creating a customer from sub. Only sign identifiers that your server has verified belong to the logged-in account.
The current decoder does not enforce iss (issuer), aud (audience), or replay prevention with jti. Extra claims do not grant entitlements. A JWT with only contact fields can prefill checkout, but does not attach an account. This is a Plandalf customer token, not your auth provider's JWT or a REST API bearer key.
Browser identity lifecycle
Fetch the signed token from your authenticated backend, then identify before opening checkout:
plandalf.ready(async () => {
plandalf.identify(customerJwt);
await plandalf.present("pro-plan");
});The SDK persists the token in local storage as plandalf_user and restores it after page reload. It does not automatically refresh expired JWTs. Supply a fresh token from your backend after login or expiry, replace it when switching accounts, and call plandalf.reset() on logout.
A plain email string can be stored by the SDK, but the checkout backend does not accept it as signed customer identity. Use checkout properties for email prefill; do not use identify("jane@example.com") to attach an account.
Without identify(), checkout is anonymous. reset() clears the stored user token and preserves the anonymous device ID. It does not erase separately persisted cks_ session resume tokens or close a checkout that is already open. Close active checkout handles and clear session continuity when switching accounts if you enabled it.
Per-call identity
Pass user to one checkout without changing the global browser identity:
await plandalf.present("pro-plan", {
user: customerJwt
});The same user override is supported by mount(). Identity applies when a new checkout starts; calling identify() does not rebind a checkout that is already open.
Validation and troubleshooting
The inspected SDK does not report token acceptance from identify(). Do not gate checkout on its return value: if (!await plandalf.identify(token)) return would stop checkout because the return value is undefined. Check the customer attached to the resulting session. Keep JWTs and signing secrets out of screenshots and debug logs.
JWT validation checks the key, signature, and token dates. It does not contact Stripe, select an offer, or verify the customer/subscription binding. The session endpoint rejects identity from another offer organization. Your server must verify ownership of every customer or subscription identifier it signs; do not treat JWT decoding as that verification.
- An unknown key identifier, inactive signing key, wrong signature, unsupported algorithm, expired token, or not-yet-valid token is rejected by the decoder.
- A token signed by a different organization is ignored for that checkout.
- The SDK checkout endpoint currently falls back to anonymous checkout for rejected tokens. A successful HTTP response or visible checkout is not proof that identity was accepted.
- A valid
subshould result in a customer whoseexternal_idmatches your user ID, attached to the checkout session. Verify that association when testing your integration. - Deactivating the signing key rejects subsequent JWT validation with that key. It does not close already-created checkout sessions.
cks_resume tokens are separate from customer JWTs;identify()is not their API.
Identity links checkout to a customer; it does not establish that the customer has paid. Your backend remains responsible for feature authorization and subscription ownership.
Test checkout requirements
For the inspected POST /client/sessions route, requesting { mode: "test" } requires an offer configured for test mode or an authenticated caller who belongs to the offer's organization. A JWT mode claim or test-domain setting does not satisfy this route's check.
Connect Stripe (Test) and verify that it is the selected payment connection before submitting payment. The inspected backend falls back to a live Stripe connection if no test connection exists, even when the session is marked test. A mode option or session label alone does not establish sandbox payments.