Quickstart — zero to paywall in 5 minutes
SDK 3.0 covers three scenarios with one npm package: a modal in the browser, popup/content-script in a Chrome extension, and headless calls from a server. All three share one paywallId — you don’t need separate paywalls per channel.
Using an AI coding agent? Feed it llms-sdk-v3.txt — a single-file pack with the full SDK 3.0 API and integration patterns. Cursor, Claude Code, Copilot and the like produce far more accurate code with it in context.
Before you start
- A paywall is created in the dashboard → “Paywalls” → “New Paywall”. Remember the
paywallId(the number in the URL). - At least one payment processor is connected (Stripe / Paddle / Chargebee / Freemius).
- If you’ll be using the API Gateway (metered AI calls) — also have a
providerId(UUID from the “API Providers” section in the dashboard).
apiOrigin for every example is https://YOUR_DOMAIN. If you have a custom domain, substitute yours.
Pick a scenario
Three integration paths — pick by how much UI you want to own and where your auth lives. See Overview for a side-by-side comparison.
Drop-in (Web)
Drop-in PaywallUI — Web / SPA
Install the npm package, open the paywall with one line, listen for checkout success. Ready-made modal in a Shadow DOM with built-in email / OTP / OAuth login.
Install
pnpm add @monetize.software/sdk
# or: npm i @monetize.software/sdkInitialize
import { PaywallUI } from '@monetize.software/sdk/ui';
const paywall = new PaywallUI({
paywallId: '3',
apiOrigin: 'https://YOUR_DOMAIN',
auth: true // turns on built-in email / OTP / OAuth login
});auth: true is enough for most integrations — the SDK creates an AuthClient itself, persists the session, refreshes tokens. If you already have your own AuthClient (or want a hybrid identity), pass identity or auth: <AuthClient> explicitly.
Open the paywall
document.getElementById('upgrade-btn').addEventListener('click', () => {
paywall.open();
});Already rendering your own pricing cards and want the click to skip the plan-picker step? Use paywall.checkout(priceId) — the modal still handles preauth signin, popup-blocked retry, and the awaiting-payment screen:
document.querySelectorAll<HTMLButtonElement>('[data-price-id]').forEach((el) => {
el.addEventListener('click', () => paywall.checkout(el.dataset.priceId!));
});React to the purchase
paywall.on('purchase_completed', ({ priceId, sessionId }) => {
// update user state, unlock the feature
});
paywall.on('close', () => {
// user closed the modal without buying
});
paywall.on('error', (err) => {
console.error('paywall error', err);
});Full event list — in the PaywallUI API.
What’s next: BillingClient — when you need prices and user state before opening the modal. Authentication — customising the login flow.
What all scenarios have in common
- One
paywallIdacross drop-in / custom UI / headless — switch paths without migrating data. - One
apiOrigin(or one custom domain), or none at all for pure headless. - Unified auth model —
AuthClientbehaves the same way in browser scenarios; headless usesapiKey+identityand gets the same outcome on the backend. - Identical event signatures —
purchase_completed,authChange,userChangehave the same shape whether you’re listening fromPaywallUI,BillingClient, or webhooks.