No CORS errors in the service worker
MV3 moved network calls out of the popup. Most SDKs never adapted. crxpay routes every request through chrome.runtime.sendMessage, so billing traffic never touches a cross-origin pre-flight.
Browsers · Chrome (MV3)
No CORS drama. No fetch-from-popup bugs. No dead subscriptions when the service worker sleeps. Drop the SDK in, wire one handler, ship.
Offline-signed — status verified 4s ago.
MV3, done right
MV3 moved network calls out of the popup. Most SDKs never adapted. crxpay routes every request through chrome.runtime.sendMessage, so billing traffic never touches a cross-origin pre-flight.
Service workers evict memory after 30 seconds. We persist a signed entitlement blob in chrome.storage.local — so your paywall disappears when the user cancels, not when Chrome garbage-collects.
No manifest hacks, no background.js gymnastics. Import the SDK, call init() with your public key, and get typed entitlement checks in content scripts, popups, and the worker itself.
The subscription state is a plain observable. Wire it to React hooks, Vue refs, Svelte stores, or a raw callback — crxpay does not care how you render your popup.
Quickstart
Real code. Copy-paste ready. This is the entire MV3 integration — we do not hide the rest in “advanced configuration” docs.
Add @crxpay/sdk to your extension project. It ships as ESM and works with Vite, webpack, or esbuild out of the box.
One call in background.ts. The SDK wires message handlers so popups and content scripts never have to do their own fetch.
Pop-up, content script, options page — one async call, typed return, zero network round-trip on the happy path.
crxpay opens a new tab to Stripe-hosted checkout, listens for the webhook, then signs a fresh cache entry. No redirect gymnastics required.
pnpm add @crxpay/sdk// background.ts — MV3 service worker
import { crxpay } from '@crxpay/sdk';
crxpay.init({
apiKey: 'crxpay_pub_live_…',
// the SDK uses chrome.storage.local
// for the offline-signed entitlement cache
});// popup.tsx
import { crxpay } from '@crxpay/sdk';
const sub = await crxpay.checkSubscription();
if (sub.hasEntitlement('pro')) {
unlockPro();
} else {
showPaywall();
}// on "Upgrade" click
await crxpay.openCheckout({
priceId: 'price_1Pqk…',
successUrl: 'chrome-extension://…/success.html',
});Compatibility
If it speaks MV3, crxpay works. The same zip you upload to the Chrome Web Store runs unmodified on Edge, Brave, Opera, Arc, and Vivaldi.
Pitfalls
fetch() inside popup.htmlTriggers CORS pre-flight every time. Breaks when the host blocks cross-origin.
chrome.runtime.sendMessage → service workercrxpay marshals the call through the worker. The pop-up never touches the network.
Store sub status in memoryThe service worker evicts after 30s of idle. Your Pro badge disappears.
Signed cache in chrome.storage.localHMAC-signed blob survives eviction, restarts, and offline mode. Verified by the SDK on read.
Hardcode price IDs in the extensionEvery price change requires a full re-submission to the Chrome Web Store.
Fetch offerings from crxpayShip once. Change prices, trials, and paywall copy from the dashboard.
Our entire suite of features comes standard — and your first $2,500 in tracked revenue is free.