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.

OptionType
publishableKeystringRequired. 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
apiUrlstringDefault https://api.cashsdk.com
timeoutMsnumberPer request, including getSessionToken. Default 15000, from 100 to 60000
fetchtypeof fetchFor tests or custom transports

Client methods#

Every method that makes a request accepts { signal } and rejects with a CashSDKError.

MethodReturnsNotes
initialize()ClientSnapshotLoads the customer and their access. Repeated calls share one load
getCustomer(){ customerId, environment }
getEntitlements()EntitlementSnapshotUpdates the snapshot. An older read never replaces a newer one
refreshEntitlements()EntitlementSnapshotSame as getEntitlements
getSubscriptions(){ customerId, environment, subscriptions }Needs subscriptions:read
getOfferings()OfferingsOnly 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 })CheckoutNeeds checkout:create. See Getting paid
getCheckout(id)CheckoutOnly 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)voidChanges 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()voidClears state and listeners. The client cannot be used again
subscribe(listener)() => voidCalled on every state change. Returns an unsubscribe function
getSnapshot()ClientSnapshotThe 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#

ts
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#

ScopeDefaultAllows
customer:readyesgetCustomer
entitlements:readyesgetEntitlements, waitForEntitlement
subscriptions:readyesgetSubscriptions
offerings:readyesgetOfferings
checkout:createnocheckout
checkout:readnogetCheckout, waitForCheckout
billing:managenoopenBillingPortal

Errors#

Every failure is a CashSDKError:

Field
codeStable. Decide on it, never on message
messageFor people. It can change
statusThe HTTP status, or 0 when no response arrived
retryableWhether the same request can succeed later. Retry a checkout with the same idempotency key
retryAfterMsHow long to wait first, when the server said
requestIdQuote 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

CodeStatusMeaning
invalid_web_session401No session, or it expired or was revoked. The client asks your backend for a new one once
invalid_publishable_key403The 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_allowed403The page's origin is not allowed, sessions are off in this environment, or the session belongs to another origin
invalid_origin400createWebSession got an origin that is not a bare HTTPS or chrome-extension:// origin
insufficient_scope403The session lacks the scope for this call, or a restricted server key lacks web_sessions:write
server_only403A server route was called from a browser
invalid_server_key401The server key is missing, revoked or not a server key
environment_mismatch400The key, session or response belongs to the other environment
web_unavailable403The app has none of Web, Chrome extension, macOS with Stripe or Stripe only
customer_erased403The customer was erased
invalid_customer400The customer ID is missing or longer than 200 characters
invalid_request400A field is missing or malformed
rate_limited429Too many requests. Wait retryAfterMs

Checkout

CodeStatusMeaning
already_subscribed409The customer has active access in this product group, from any store. Open the billing portal instead
already_owned409The customer owns this one-time product
already_purchased409Another checkout for this plan was just paid. Wait for access with waitForEntitlement
checkout_in_progress409Another checkout for this plan is being created. Retryable, with Retry-After: 2
checkout_uncertain503CashSDK could not confirm the request. Retry with the same idempotency key
checkout_unavailable403, 503Checkout is off for the app or the environment, or the checkout service is unavailable
unsupported_checkout400The package is not a Stripe product in the current offering
invalid_mapping400The product or its Stripe price is not a fixed price checkout can sell
catalog_price_mismatch409The Stripe price differs from the catalog
connection_unavailable409No active Stripe connection made with an API key in this environment
connection_rejected409Stripe refused the connection's key
provider_rejected409Stripe refused the request and created nothing
provider_account_changed409The connection's key now belongs to another Stripe account. Start a new attempt
provider_request_failed, provider_rate_limited503Stripe did not answer, or asked CashSDK to slow down. Retry with the same idempotency key
provider_response_invalid409, 503Stripe's answer could not be used
idempotency_conflict409The idempotency key was used for a different request
invalid_idempotency_key, invalid_return_url, invalid_checkout400Fix the input
checkout_not_found404No such checkout for this customer

Billing portal

CodeStatusMeaning
no_billing_account404The customer has no Stripe customer in this environment, so there is no portal to open
billing_account_conflict409The only Stripe customer found is linked to another customer too. Correct the link first
billing_portal_unavailable503The billing portal service did not answer. Try again

Raised by the SDKs

CodeStatusMeaning
invalid_configuration0An 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_entitlement0waitForEntitlement was called without an identifier
checkout_not_ready0openCheckoutTab got a checkout that is not waiting for the customer
identity_changed, signed_out, destroyed0The client changed customer, signed out or was destroyed while the call ran
timeout0No answer within timeoutMs
network_error0CashSDK could not be reached: offline, DNS, or a refused or reset connection. Retryable. cause holds the original fetch error
invalid_responseanyThe response did not have the documented shape, or named a checkout or portal URL the SDK does not open
http_<status>anyA failure whose response carried no code
invalid_signature, invalid_webhook, invalid_webhook_configuration0From 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.

RouteCalled by
POST /v1/web/sessionscreateWebSession
POST /v1/web/sessions:revokerevokeWebSessions
DELETE /v1/web/sessions/currentlogout
GET /v1/web/customergetCustomer
GET /v1/web/entitlementsgetEntitlements
GET /v1/web/subscriptionsgetSubscriptions
GET /v1/web/offeringsgetOfferings
POST /v1/web/checkoutscheckout
GET /v1/web/checkouts/{checkoutId}getCheckout
POST /v1/web/billing-portalopenBillingPortal
POST /v1/web/customers/{customerId}/billing-portalcreateBillingPortalSession (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, AbortController and ES2022; Node.js 20.19+ or 22.12+; React 18 and 19.