Web SDK reference
Every method, type, option, error code, scope and limit of cashsdk-web, cashsdk-react and cashsdk-node, and the HTTP routes behind them.
The three packages are released together at one version. The current one is on the Web SDK page.
createCashSDK(options)#
cashsdk-web. Returns a CashSDKClient. Creating it makes no request.
| Option | Type | |
|---|---|---|
publishableKey | string | Required. Your app's csk_pk_… key |
environment | "sandbox" | "production" | Required. Must match the session your backend issues |
getSessionToken | ({ signal }) => Promise<string | { token, expiresAt? }> | Required. Calls your backend and returns its session |
apiUrl | string | Default https://api.cashsdk.com |
timeoutMs | number | Per request, including getSessionToken. Default 15000, from 100 to 60000 |
fetch | typeof fetch | For tests or custom transports |
Client methods#
Every method that makes a request accepts { signal } and rejects with a CashSDKError.
| Method | Returns | Notes |
|---|---|---|
initialize() | ClientSnapshot | Loads the customer and their access. Repeated calls share one load |
getCustomer() | { customerId, environment } | |
getEntitlements() | EntitlementSnapshot | Updates the snapshot. An older read never replaces a newer one |
refreshEntitlements() | EntitlementSnapshot | Same as getEntitlements |
getSubscriptions() | { customerId, environment, subscriptions } | Needs subscriptions:read |
getOfferings() | Offerings | Only web packages; App Store and Google Play products are left out |
waitForEntitlement(id, options?) | { snapshot, active } | Reads until the entitlement is active or time runs out |
checkout(input, { idempotencyKey }) | Checkout | Needs checkout:create. See Getting paid |
getCheckout(id) | Checkout | Only the customer who started it can read it |
waitForCheckout(id, options?) | { checkout, settled } | Reads until the checkout is final or time runs out |
openBillingPortal({ returnUrl }) | { url } | A Stripe customer portal session. Needs billing:manage. Never navigates. See Getting paid |
setSessionTokenProvider(fn) | void | Changes the customer: drops all state, cancels requests, ends the previous session |
logout() | Promise<void> | Clears state at once, then ends the session at CashSDK |
destroy() | void | Clears state and listeners. The client cannot be used again |
subscribe(listener) | () => void | Called on every state change. Returns an unsubscribe function |
getSnapshot() | ClientSnapshot | The current state, frozen |
Wait options: timeoutMs (default 30000, at most 300000), intervalMs (first delay, default 1000,
from 100 to 5000, growing to 5000), and signal. A wait keeps going through failures a retry can
fix, and ends at once on sign-out, on a change of customer or when signal aborts.
Types#
type ClientSnapshot = {
status: "idle" | "loading" | "ready" | "error" | "signed_out" | "destroyed";
customer: { customerId: string; environment: Environment } | null;
entitlements: EntitlementSnapshot | null;
error: Error | null;
};
type EntitlementSnapshot = {
customerId: string;
environment: "sandbox" | "production";
revision: number;
computedAt: string; // ISO 8601
entitlements: readonly {
identifier: string; // for example "pro"
name: string;
active: boolean;
rank: number | null;
source: string; // subscription, purchase, manual_grant, ...
expiresAt?: string | null; // null when it does not expire
}[];
};
type WebSubscription = {
productIdentifier: string;
displayName: string | null;
provider: string | null; // stripe, app-store, play-store, ...
status: string; // active, expired, revoked, in_grace_period, ...
active: boolean; // grants access right now
expiresAt: string | null;
autoRenew: boolean;
trial: boolean;
};
type Offerings = {
current: {
id: string;
identifier: string;
displayName: string | null;
packages: readonly {
id: string;
identifier: string; // for example "$monthly"
purchasable: boolean; // this session can start a checkout for it
product: {
identifier: string;
displayName: string | null;
type: string; // auto_renewable, non_consumable, ...
provider: string; // stripe, ...
amountMinor: string | null; // integer minor units as a string, "1299" is 12.99
currency: string | null; // ISO 4217
duration: string | null; // ISO 8601, for example "P1M"
};
}[];
} | null;
};
type Checkout = {
id: string;
provider: "stripe";
environment: "sandbox" | "production";
status: "pending" | "requires_action" | "complete" | "expired" | "failed" | "needs_review";
paymentStatus: string | null; // Stripe's: paid, unpaid, no_payment_required
accessStatus: "check_entitlements";
action: { type: "redirect"; url: string } | null;
expiresAt: string | null;
};
checkout input: packageId, successUrl, cancelUrl (both on the session's origin, with no
fragment; for an extension, on one of the app's allowed HTTPS origins), and optionally
connectionId. The idempotency key is 16 to 128 letters, digits, _ or -. Replaying a key
returns the checkout it created.
openBillingPortal input: returnUrl, on the session's origin with no fragment, at most 2,048
characters. It resolves to { url }, an https://billing.stripe.com/ link that expires within
minutes.
cashsdk-web/extension#
For Manifest V3 extension pages and service workers. See Chrome extensions.
| Export | |
|---|---|
createExtensionCashSDK({ runtime, ...options }) | A CashSDKClient for the extension. runtime is chrome.runtime; the other options are those of createCashSDK. Each request names the extension's origin in X-CashSDK-Client-Origin, for the requests where Chrome sends no Origin |
extensionOrigin(runtime) | chrome-extension://<id>, the origin to allow. Throws invalid_configuration outside an extension page or service worker, such as in a content script |
openCheckoutTab(tabs, checkout) | Opens checkout.action.url in a new active tab. Throws checkout_not_ready unless the checkout is requires_action with a redirect, and invalid_response for a URL that is not Stripe Checkout |
openBillingPortalTab(tabs, portal) | Opens the portal URL from openBillingPortal in a new active tab. Throws invalid_response for a URL that is not Stripe's portal |
tabs is chrome.tabs, or anything with a create({ url, active }) method. The types
ExtensionRuntime and ExtensionTabs describe the parts of the Chrome APIs it uses.
cashsdk-react#
CashSDKProvider({ client, children }) and the hooks useCashSDK, useCashSDKState, useCustomer,
useEntitlements, useSubscriptions, useOfferings, useCheckout and useBillingPortal.
See React and Next.js.
cashsdk-node#
new CashSDKServer({ secretKey, environment, apiUrl?, timeoutMs?, fetch? }) | A csk_sk_ or csk_rk_ key. A test key needs sandbox, a live key production |
createWebSession({ customerId, origin, scopes?, ttlSeconds? }) | { token, expiresAt, expiresInSeconds, customerId, environment, scopes } |
revokeWebSessions(customerId) | { revoked } |
getEntitlements(customerId) | { entitlements, tier, tier_identifier, app_user_id, customer_exists, environment } |
createBillingPortalSession({ customerId, returnUrl, configuration? }) | { customerId, environment, provider, url }. returnUrl is HTTPS, or loopback HTTP in sandbox. configuration is a Stripe bpc_… id |
verifyWebhook(rawBody, signature, { secrets, toleranceSeconds?, maxBytes?, now? }) | The parsed event { id, type, ... } |
Session scopes#
| Scope | Default | Allows |
|---|---|---|
customer:read | yes | getCustomer |
entitlements:read | yes | getEntitlements, waitForEntitlement |
subscriptions:read | yes | getSubscriptions |
offerings:read | yes | getOfferings |
checkout:create | no | checkout |
checkout:read | no | getCheckout, waitForCheckout |
billing:manage | no | openBillingPortal |
Errors#
Every failure is a CashSDKError:
| Field | |
|---|---|
code | Stable. Decide on it, never on message |
message | For people. It can change |
status | The HTTP status, or 0 when no response arrived |
retryable | Whether the same request can succeed later. Retry a checkout with the same idempotency key |
retryAfterMs | How long to wait first, when the server said |
requestId | Quote it to support. It is also the X-Request-Id response header |
Error codes#
Some input errors are also raised by the SDK before any request is sent, with status 0.
Sessions and keys
| Code | Status | Meaning |
|---|---|---|
invalid_web_session | 401 | No session, or it expired or was revoked. The client asks your backend for a new one once |
invalid_publishable_key | 403 | The publishable key is missing, malformed, revoked or expired, or is not a publishable key. A configuration error: the client does not ask for a new session |
origin_not_allowed | 403 | The page's origin is not allowed, sessions are off in this environment, or the session belongs to another origin |
invalid_origin | 400 | createWebSession got an origin that is not a bare HTTPS or chrome-extension:// origin |
insufficient_scope | 403 | The session lacks the scope for this call, or a restricted server key lacks web_sessions:write |
server_only | 403 | A server route was called from a browser |
invalid_server_key | 401 | The server key is missing, revoked or not a server key |
environment_mismatch | 400 | The key, session or response belongs to the other environment |
web_unavailable | 403 | The app has none of Web, Chrome extension, macOS with Stripe or Stripe only |
customer_erased | 403 | The customer was erased |
invalid_customer | 400 | The customer ID is missing or longer than 200 characters |
invalid_request | 400 | A field is missing or malformed |
rate_limited | 429 | Too many requests. Wait retryAfterMs |
Checkout
| Code | Status | Meaning |
|---|---|---|
already_subscribed | 409 | The customer has active access in this product group, from any store. Open the billing portal instead |
already_owned | 409 | The customer owns this one-time product |
already_purchased | 409 | Another checkout for this plan was just paid. Wait for access with waitForEntitlement |
checkout_in_progress | 409 | Another checkout for this plan is being created. Retryable, with Retry-After: 2 |
checkout_uncertain | 503 | CashSDK could not confirm the request. Retry with the same idempotency key |
checkout_unavailable | 403, 503 | Checkout is off for the app or the environment, or the checkout service is unavailable |
unsupported_checkout | 400 | The package is not a Stripe product in the current offering |
invalid_mapping | 400 | The product or its Stripe price is not a fixed price checkout can sell |
catalog_price_mismatch | 409 | The Stripe price differs from the catalog |
connection_unavailable | 409 | No active Stripe connection made with an API key in this environment |
connection_rejected | 409 | Stripe refused the connection's key |
provider_rejected | 409 | Stripe refused the request and created nothing |
provider_account_changed | 409 | The connection's key now belongs to another Stripe account. Start a new attempt |
provider_request_failed, provider_rate_limited | 503 | Stripe did not answer, or asked CashSDK to slow down. Retry with the same idempotency key |
provider_response_invalid | 409, 503 | Stripe's answer could not be used |
idempotency_conflict | 409 | The idempotency key was used for a different request |
invalid_idempotency_key, invalid_return_url, invalid_checkout | 400 | Fix the input |
checkout_not_found | 404 | No such checkout for this customer |
Billing portal
| Code | Status | Meaning |
|---|---|---|
no_billing_account | 404 | The customer has no Stripe customer in this environment, so there is no portal to open |
billing_account_conflict | 409 | The only Stripe customer found is linked to another customer too. Correct the link first |
billing_portal_unavailable | 503 | The billing portal service did not answer. Try again |
Raised by the SDKs
| Code | Status | Meaning |
|---|---|---|
invalid_configuration | 0 | An option is missing or invalid: the key, environment, getSessionToken, an API URL that is not HTTPS, timeoutMs or a wait's timing, or an extension client created outside an extension page or service worker |
invalid_entitlement | 0 | waitForEntitlement was called without an identifier |
checkout_not_ready | 0 | openCheckoutTab got a checkout that is not waiting for the customer |
identity_changed, signed_out, destroyed | 0 | The client changed customer, signed out or was destroyed while the call ran |
timeout | 0 | No answer within timeoutMs |
network_error | 0 | CashSDK could not be reached: offline, DNS, or a refused or reset connection. Retryable. cause holds the original fetch error |
invalid_response | any | The response did not have the documented shape, or named a checkout or portal URL the SDK does not open |
http_<status> | any | A failure whose response carried no code |
invalid_signature, invalid_webhook, invalid_webhook_configuration | 0 | From verifyWebhook in cashsdk-node. See Server SDK |
A failure without a code of its own gets one from its status: forbidden, not_found,
conflict, request_too_large, service_unavailable or internal_error.
HTTP routes#
The packages call these. Browser routes need Authorization: Bearer csk_pk_…, the
X-CashSDK-Web-Session header and the page's Origin. An extension client also sends its origin
in X-CashSDK-Client-Origin, which counts only when Chrome sends no Origin. Server routes need a
server key and refuse any request with an Origin header.
| Route | Called by |
|---|---|
POST /v1/web/sessions | createWebSession |
POST /v1/web/sessions:revoke | revokeWebSessions |
DELETE /v1/web/sessions/current | logout |
GET /v1/web/customer | getCustomer |
GET /v1/web/entitlements | getEntitlements |
GET /v1/web/subscriptions | getSubscriptions |
GET /v1/web/offerings | getOfferings |
POST /v1/web/checkouts | checkout |
GET /v1/web/checkouts/{checkoutId} | getCheckout |
POST /v1/web/billing-portal | openBillingPortal |
POST /v1/web/customers/{customerId}/billing-portal | createBillingPortalSession (server) |
Responses are never cached (Cache-Control: no-store), and failures are
{ "error": { "code", "message", "retryable", "requestId" } }.
Limits#
- Sessions last 60 to 600 seconds, default 600. Reuse one until it expires.
- Minting sessions: 20,000 a minute per server key and 120 a minute per customer, counted apart from other server calls.
- Up to 20 allowed origins per environment.
- Browser routes: 1,000 requests a minute per customer. Other server routes: 1,000 requests a minute per key.
- Billing portal sessions: 20 a minute per customer, from the browser and the server together.
- A Stripe Checkout Session started by the SDK expires after 60 minutes.
- Supported: browsers with
fetch,AbortControllerand ES2022; Node.js 20.19+ or 22.12+; React 18 and 19.