Stripe Billing in a Chrome Extension: The Right Way vs. The Wrong Way

Wiring Stripe into a browser extension sounds straightforward until you actually try it. The first approach most developers reach for — creating a checkout session from the popup — runs imme

  • Jenny Wilson Jenny Wilson
  • date icon

    Sunday, Mar 01, 2026

Stripe Billing in a Chrome Extension: The Right Way vs. The Wrong Way

Wiring Stripe into a browser extension sounds straightforward until you actually try it. The first approach most developers reach for — creating a checkout session from the popup — runs immediately into CORS restrictions. The second approach — using Stripe.js client-side — works but exposes your pricing logic and forces you to handle redirects awkwardly. The third attempt usually involves an external backend proxy, which adds infrastructure you should not need.

There is a correct architecture for this. It uses the extension’s background service worker as the Stripe session creator, and it is simpler than it sounds once you see it.

Why the Obvious Approaches Fail

Approach 1: Create the checkout session from the popup

Stripe’s checkout session creation (POST /v1/checkout/sessions) requires your secret key. If you make this request directly from the popup’s JavaScript, your secret key is embedded in the extension source code — which anyone can read by loading your extension as unpacked. This is a critical security vulnerability.

Even if you were willing to accept that risk, Stripe’s API does not set CORS headers that allow requests from extension origins. The request is blocked before it reaches Stripe.

Approach 2: Use Stripe.js in the popup

Stripe.js is designed for web pages. It can tokenize card details and redirect to Stripe-hosted checkout pages using a publishable key. But for subscription management and customer portal access, you still need server-side session creation. And the redirect from an extension popup to a Stripe-hosted page, then back to a success URL, does not work cleanly — extension popups close when the browser navigates away.

Approach 3: External proxy server

Some developers spin up a small backend (a Vercel edge function, a Lambda) just to create Stripe sessions. This works but adds an infrastructure dependency, a separate codebase to maintain, and costs money. For an extension that already has a Supabase or Firebase backend, it creates a third system.

The Right Architecture: Service Worker as Stripe Backend

The background service worker in a MV3 Chrome extension can make fetch requests to any URL without CORS restrictions. It runs in an isolated context, separate from any web page, and is treated as a trusted first-party origin by browsers.

This makes it the correct place to call Stripe’s API with your secret key.

The flow works like this:

  1. The user clicks “Upgrade” in the popup
  2. The popup sends a typed runtime message to the background service worker: { type: 'CREATE_CHECKOUT_SESSION', priceId: 'price_xxx' }
  3. The service worker receives the message, creates a Stripe checkout session using fetch with your secret key in the Authorization header
  4. The service worker receives the session URL from Stripe
  5. The service worker calls chrome.tabs.create({ url: sessionUrl }) to open the Stripe checkout page in a new tab
  6. After the user completes checkout, Stripe fires a webhook to your backend (Supabase Edge Function or Firebase Cloud Function), which updates the user’s subscription status in the database

The secret key never touches client-side code. The CORS problem does not exist. The user gets a clean Stripe-hosted checkout experience in a full browser tab.

Handling the Customer Portal

The customer portal (for managing subscriptions, updating payment methods, canceling) uses the same pattern:

  1. Popup sends { type: 'CREATE_PORTAL_SESSION' }
  2. Service worker creates a portal session via POST /v1/billing_portal/sessions
  3. Service worker opens the portal URL in a new tab

The only difference is that the portal session requires a Stripe customer ID, which you retrieve from your database before making the API call.

Secret Key Safety

In this architecture, your Stripe secret key lives in the extension’s environment variables, bundled into the service worker at build time. It is not visible in the popup or content script code. However, it is present in the unpacked extension files on the user’s machine.

This is an accepted trade-off for MV3 extensions — the alternative (a separate proxy server) is more complex for marginally better security. If you are concerned about this, the cleanest solution is to not bundle the key at all: instead, have the service worker call your Supabase Edge Function or Firebase Cloud Function, which holds the secret key server-side and makes the Stripe API call. LightningAddon supports both patterns.

What LightningAddon Ships

LightningAddon ships the service worker billing pattern fully implemented. The RuntimeMessageMap in @repo/core includes typed message definitions for CREATE_CHECKOUT_SESSION and CREATE_PORTAL_SESSION. The background service worker handler calls your configured backend (Supabase or Firebase), which creates the Stripe session and returns the URL. The flow from “user clicks upgrade” to “Stripe checkout opens in new tab” is already working when you clone the repo.

You configure your Stripe price IDs and keys in environment variables. Everything else is already done.

Blog

Read More Posts

Your Trusted Partner in Data Protection with Cutting-Edge Solutions for
Comprehensive Data Security.

How to Get Your First 100 Paying Users for a Browser Extension
date icon

Tuesday, Mar 17, 2026

How to Get Your First 100 Paying Users for a Browser Extension

Most browser extension SaaS products never reach 100 paying users — not because they are bad products, but because the d

Read More
Supabase vs Firebase for Your Extension Backend: A Practical 2026 Comparison
date icon

Sunday, Mar 15, 2026

Supabase vs Firebase for Your Extension Backend: A Practical 2026 Comparison

If you are starting a browser extension SaaS, you need a backend for auth, data storage, and Stripe webhook handling. Su

Read More
How to Update Your Extension Without Breaking Existing Users
date icon

Saturday, Mar 14, 2026

How to Update Your Extension Without Breaking Existing Users

Browser extensions store data in chrome.storage.local and chrome.storage.sync. Unlike a server-side database, you do

Read More
LightningAddon Chrome, Firefox & Safari extension framework call to action

Stop Rebuilding the Same Foundation. Start Shipping.

Auth, billing, multi-browser builds, typed messaging — every extension SaaS needs the same boring foundation. LightningAddon ships all of it, battle-tested and ready in 15 minutes. Pay once, own it forever.

Get LightningAddon