Web SDK
Add subscriptions and customer access to a web app: browser, React and Node.js packages that share one entitlement model with your iOS and Android apps.
The web SDK tells your pages and your server what a signed-in customer has paid for. It reads the same entitlements your iOS and Android apps use, so a customer who subscribed on an iPhone is Pro in your web app too, and a web subscription unlocks the mobile apps.
| Package | Runs in | Use it for |
|---|---|---|
cashsdk-web | The browser | The customer's access, subscriptions and offering; checkout |
cashsdk-react | The browser, with React 18 or 19 | Hooks on top of cashsdk-web, including for the Next.js App Router |
cashsdk-node | Your server, Node.js 20.19+ or 22.12+ | Customer sessions, access checks before serving paid content, webhook signatures |
Web SDK 1.0.0 · on npm
Generally available. Install the packages from npm. Source: github.com/CashSDK/cashsdk-web.
- Authenticated browser sessions (Generally available)
Short-lived sessions your backend issues for one signed-in customer and one browser origin. Revocable, and never holding a server key.
- Customer access, subscriptions and offerings (Generally available)
Read the customer's entitlements, their subscriptions from every store, and the current offering, in the browser and on your server.
- Access from your own Stripe checkout (Generally available)
Keep your Stripe Checkout. Connect Stripe in the Marketplace, and every subscription grants and ends access the same way App Store and Google Play purchases do, including trials, add-ons, refunds and a week of access while Stripe retries a failed renewal.
- One-time purchases from your Stripe checkout (Generally available)
A paid one-time Checkout Session unlocks lifetime products, one purchase per line item, and a full refund takes the unlock back.
- Import Stripe prices (Generally available)
Pick prices from the connected Stripe account on the Products page, and CashSDK creates the matching products with the amount, currency and period Stripe states.
- Stripe customer portal (Generally available)
Open Stripe's hosted portal for the signed-in customer's own Stripe account, so they can update a card or cancel: openBillingPortal in the browser, useBillingPortal in React, createBillingPortalSession on your server.
- Stripe checkout started by the SDK (Generally available)
Hosted Stripe Checkout for your recurring and one-time Stripe prices, with free trials from intro offers, optional promotion codes and Stripe Tax, durable retries and a return page helper.
- PayPal payment tracking (In development)
Verified PayPal payment and refund events. Access lifecycle and PayPal checkout are not available yet.
- Advanced web commerce (Planned)
Plan changes, seats, credits, usage billing and embedded checkout.
How it works#
The browser never holds a secret key and never decides who the customer is:
- Your backend signs the customer in, with whatever authentication you already use.
- It asks CashSDK for a web session for that customer and your site's origin. The session lasts at most ten minutes, and only works from that origin.
- The browser uses the session and your publishable key to read the customer's access.
- Purchases reach CashSDK as signed events from your payment provider. Returning from a checkout page grants nothing by itself.
- Your server checks access again before it returns anything paid for.
Install#
npm install cashsdk-web cashsdk-node
# or: pnpm add cashsdk-web cashsdk-node
# or: yarn add cashsdk-web cashsdk-nodeFor React, add cashsdk-react as well. See React and Next.js.
Set up the dashboard#
In the dashboard, create an app and choose Web. A web-only app needs no App Store or Google Play identifier. To share access with your mobile apps, add Web to the app they already use instead, so all three read the same entitlements.
Open Your App → Web and Stripe. Under Sandbox origins, add the exact origin of your
site, such as https://app.example.com, with no path and no trailing slash, and tick
Allow browser sessions. Sandbox also accepts http://localhost:3000 and other loopback
addresses for development. Wildcards are refused. Changing the origins, or turning sessions
on or off, revokes every open session in that environment.
In Developers → API keys, copy the app's publishable key (csk_pk_…). Then create a
restricted key for your server, in the environment you are building for (csk_rk_test_…
for sandbox, csk_rk_live_… for production), with Create customer web sessions and
Customers. Keep it on your server.
Create the entitlement your features check, such as pro, under Your App → Entitlements.
Then connect your payment provider so purchases grant it: see
Getting paid on the web.
Issue sessions from your backend#
Add one endpoint that your signed-in pages call. It must only answer a customer your own authentication has verified.
import { CashSDKServer } from "cashsdk-node";
export const cashsdkServer = new CashSDKServer({
secretKey: process.env.CASHSDK_SECRET_KEY!, // csk_rk_test_… in sandbox
environment: "sandbox",
});
import { cashsdkServer } from "@/lib/cashsdk-server";
export async function POST(request: Request) {
const user = await requireSignedInUser(request); // your authentication
const session = await cashsdkServer.createWebSession({
customerId: user.id, // never a value the browser sent
origin: "https://app.example.com", // the exact origin you allowed
});
return Response.json(session, { headers: { "Cache-Control": "no-store" } });
}
Protect this endpoint against cross-site requests the way your framework does, for example with same-site cookies and a same-origin check.
A session lasts ten minutes, or less if you pass ttlSeconds. The browser client keeps it and
asks your endpoint again shortly before it expires, so a page needs one session at a time, not one
per request. Minting is rate
limited per server key and per customer: see Server SDK.
Pages that sell also ask for the checkout scopes, as
Getting paid on the web shows.
Read access in the browser#
import { createCashSDK } from "cashsdk-web";
export const cashsdk = createCashSDK({
publishableKey: "csk_pk_…",
environment: "sandbox",
getSessionToken: async ({ signal }) => {
const response = await fetch("/api/cashsdk/session", {
method: "POST",
credentials: "same-origin",
signal,
});
if (!response.ok) throw new Error("Sign in to continue");
return response.json(); // { token, expiresAt, … }
},
});
await cashsdk.initialize();
const { entitlements } = await cashsdk.getEntitlements();
const isPro = entitlements.some((item) => item.identifier === "pro" && item.active);
Create one client for the page and reuse it. It asks your endpoint for a session the first time it needs one.
| Call | Returns |
|---|---|
initialize() | Loads the customer and their access. Safe to call more than once. |
getEntitlements() | Active entitlements, with identifier, active and expiresAt |
getSubscriptions() | Subscriptions from every store and provider, for an account page |
getOfferings() | The current offering and its web packages, with exact prices |
subscribe(listener) / getSnapshot() | Change notifications and the current state |
The full list is in the reference.
Check access on your server#
The browser decides what to show. Your server decides what to serve.
const { entitlements } = await cashsdkServer.getEntitlements(user.id);
if (!entitlements.some((item) => item.identifier === "pro")) {
return new Response("Pro is required", { status: 403 });
}
cashsdkServer is the cashsdk-node client from lib/cashsdk-server.ts. Its key needs the
Customers permission.
Sign-out and switching customers#
In the browser, logout() clears the client's state and ends this tab's session:
await cashsdk.logout();
Each tab holds its own session. In your sign-out handler, end all of them with the server client:
await cashsdkServer.revokeWebSessions(user.id); // → { revoked: 2 }
To sign in as someone else without reloading, call cashsdk.setSessionTokenProvider(getSessionToken)
after your own sign-in. It drops everything the client knew and ends the previous session.
Sandbox and production#
environment is "sandbox" or "production" and must match everywhere: the client, the server
key (test or live), the origins you allowed, and the payment provider connection. Sandbox
purchases never grant production access. Your Sandbox and Production Stripe connections live side
by side, so you can keep testing in Sandbox after you go live.
Go live#
Before you take real payments, follow Go live with Stripe. It walks through the Production Stripe connection, the live prices, your production origins and the live server key, then a first live purchase and refund.
Next#
Provider, hooks, server rendering and Strict Mode.
Sessions, sign-out, access checks and webhook signatures.
Keep your Stripe checkout, or let the SDK start one.
Every method, type, error code and limit.
The source and a complete example application are on GitHub. The packages are MIT licensed.
Desktop and extensions#
The same customer sessions and Stripe account can serve Chrome extensions
and directly distributed Mac apps. Extensions use cashsdk-web/extension,
which ships in the cashsdk-web package. Mac apps use the CashSDKCommerce product of the Swift
package, from version 2.4.0. Web apps use cashsdk-web and cashsdk-react directly.