Troubleshooting
Every error you might hit, what causes it, and how to fix it.
This page is organised by symptom — search for the error message you're seeing.
Setup & manifest
"Cannot use import statement outside a module"
Your background script is loading as a classic script. Add "type": "module" to your manifest:
"background": {
"service_worker": "background.js",
"type": "module"
}
Then reload the extension in chrome://extensions. If you bundle, ensure your bundler outputs ESM (Webpack: experiments.outputModule: true; Vite: format: 'es').
"Refused to connect to https://api.crxpay.io/..."
Your extension doesn't have permission to talk to our API. Add:
"host_permissions": ["https://api.crxpay.io/*"]
After adding the permission, fully remove and reinstall the extension. Chrome doesn't always re-prompt for new host permissions on reload.
"Service worker registration failed"
99% of the time this is a syntax error in background.js that Chrome's MV3 loader silently swallows. Open the service worker console and look for the actual error.
The other 1%: you imported a file with a top-level await that fails. Wrap risky init code in a try/catch:
try {
const client = CrxPay.configure({ apiKey: '...' });
} catch (err) {
console.error('crxpay init failed', err);
}
Auth & identity
INVALID_API_KEY returned from every call
Three checks:
- The key starts with
crxpay_pub_— it's the public API key from the dashboard, not the Stripe key. - The key matches the extension you're testing — each extension has its own key, and using the wrong one fails auth.
- You're not using a deleted/rotated key — check Settings → API keys in the dashboard.
CUSTOMER_NOT_IDENTIFIED
The SDK method you called requires a customer (e.g. setAttributes). Call CrxPay.identify(email) first.
Subscription state
"User paid but hasEntitlement('pro') returns false"
In order, check:
- Did the webhook fire? Look for
[stripe-webhook] event ... type=customer.subscription.createdin your API logs. If you don't see it, the Stripe CLI isn't forwarding events — see the next section. - Did the entitlement get granted? Open Customers → that customer in the dashboard. The Entitlements tab should show
prowith an expiry. If empty, the product wasn't mapped to the entitlement — check Entitlements in the dashboard. - Is the SDK showing stale cache? Force a sync:
await CrxPay.syncSubscription(). If it now returns the right state, your cache TTL is fine; the user just needed a refresh.
Webhooks not arriving in local dev
Stripe Connect events come from the developer's connected account, not your platform account. The default stripe listen --forward-to ... only forwards platform events.
Run this instead:
stripe listen \
--forward-to localhost:8790/v1/webhooks/stripe \
--forward-connect-to localhost:8790/v1/webhooks/stripe
The CLI prints two webhook signing secrets — one for platform, one for Connect. The Connect secret is what test-mode subscription events sign with. Put it in apps/api/.dev.vars as STRIPE_WEBHOOK_SECRET_TEST.
getSubscription() returns status: 'unknown'
The SDK couldn't reach the API and has no usable cache. Three causes:
- Wrong host permissions — see "Refused to connect" above.
- Cache tampered — the HMAC verification failed and we discarded it. Look in the worker console for
CACHE_TAMPERED. Fix: have the user uninstall and reinstall, or programmatically wipe the SDK's storage key. - Brand new install with no network — totally expected. The next call with network access will resolve.
You can detect this and render a graceful UI:
const result = await CrxPay.getSubscription();
if (result.ok && result.data.status === 'unknown') {
showSubscribePromptWithRetry();
}
Checkout
"Stripe secret key is a placeholder"
Your apps/api/.dev.vars still has sk_test_YOUR_KEY_HERE. Replace it with a real test key from your Stripe CLI config (~/.config/stripe/config.toml → test_mode_api_key) and fully restart wrangler dev — env vars are cached at startup.
"customer_creation can only be used in payment mode"
Stripe rejects customer_creation in subscription-mode checkout sessions (subscriptions auto-create the customer). If you see this and you're on a recent SDK/backend, pull the latest — we removed the offending param in apps/api/src/lib/stripe.ts.
Checkout opens then immediately closes
Pop-up blocker. Make sure you're calling openCheckout() from a user-initiated event (a click handler), not in response to a programmatic event. Chrome blocks chrome.tabs.create calls that aren't traceable to a user gesture.
Customer paid but doesn't show up in the dashboard
Same root cause as "Webhooks not arriving in local dev" above — the webhook never reached your API, so we never created the customer row. Once you fix the forwarding, you can replay the missed event:
stripe events list --connected-account acct_YOUR_ID --limit 5
stripe events resend evt_XXXXX --connected-account acct_YOUR_ID
The customer.subscription.created replay will create the customer + subscription rows.
SDK behaviour
onPaid fires twice
You called CrxPay.onPaid.addListener(...) from both background and popup. That's fine — they're independent listeners. If you only want to react once, register in one place and use chrome.runtime.sendMessage to broadcast to others.
onPaid doesn't fire after a successful test payment
Likely the webhook didn't arrive (see Connect forwarding above). The event only fires after the API processes customer.subscription.created and pushes a state update.
To debug, force a sync from the popup right after the user lands on the success page:
window.addEventListener('focus', async () => {
await CrxPay.syncSubscription();
});
Extension reloaded → all SDK calls return EXTENSION_CONTEXT_INVALID
The user reloaded the extension while a content script was still alive. The content-script SDK returns this typed error instead of throwing — handle it with a "please refresh this page" banner:
const result = await CrxPay.getSubscription();
if (!result.ok && result.error.code === 'EXTENSION_CONTEXT_INVALID') {
showRefreshBanner();
return;
}
Dashboard
"Stripe account connected" but I can't create products
Stripe needs to fully verify your account before you can create live-mode products. Test mode works immediately. Click the Continue verification prompt in the Stripe Express dashboard.
My customer's email shows as stripe-cus_xxxxx@unknown.com
The webhook fired before we could fetch the customer's email from Stripe. Almost always a transient network blip. Click Resync from Stripe on the customer detail page (or replay the original event with stripe events resend).
Still stuck?
- Email support@crxpay.io — we read every message.
- Open an issue on GitHub with: SDK version, manifest excerpt, the exact error, and a minimal repro.
- For security issues only: security@crxpay.io.
Was this page helpful?
Your feedback shapes what we document next.