Stripe + Auth Starter
Start with the complete path from sign-in to paid access already working. Run it once, then replace the sample feature with your product.
Requires ChromeShip Pro
Unlock the starter with a lifetime ChromeShip Pro license.
1. Create the project
Create a project with the Stripe + Auth template. Supabase, Stripe, authentication, billing functions, and the paywall are already connected. Add your Stripe test secret key as STRIPE_SECRET_KEY in Settings > Environment, then start both runtimes from Overview.
2. Give the paywall something to show
The starter reads active prices directly from Stripe. Create a test product so the paywall has a plan to display:
- Open your Stripe dashboard — make sure the Test mode toggle is on.
- Click Add product. Give it any name, like "Pro".
- Under pricing, enter an amount — anything, even $1 — and choose One time or Recurring.
- Save the product. You do not need to copy its ID into the extension.
You can replace this test price with your real offer later without changing the paywall code.
3. Connect Stripe to the local webhook
Do this before checkout. Stripe cannot reach 127.0.0.1 directly. In Functions, turn on ngrok and copy the public URL shown for stripe-webhook.
Add that complete URL, ending in /functions/v1/stripe-webhook, to your Stripe test webhooks. Subscribe to these events:
checkout.session.completed— payment succeededcheckout.session.async_payment_succeeded— delayed payment methodscharge.refunded— refundscustomer.subscription.updated— plan changes, trials, pausescustomer.subscription.deleted— cancellationsinvoice.paid— recurring billing confirmedinvoice.payment_failed— failed renewals
Copy the webhook signing secret into STRIPE_WEBHOOK_SECRET in the local environment.
Keep ngrok running while testing payments. Without the tunnel, Stripe Checkout can succeed, but the webhook never reaches the local backend and the starter cannot activate paid access.
4. Open the popup
Open the popup and create a test account. The pricing screen loads the product you just created.
5. Pay like a real customer would
Start checkout and use Stripe's test card 4242 4242 4242 4242, any future expiry date, and any CVC.
6. Reopen the popup
Reopen the popup after the webhook arrives. The paid feature is now active for that user.
7. Now make it yours
Open src/app/popup/App.tsx. The starter feature sits inside a paywall:
<Paywall plan="pro">
<div>
<p>Unlocked. Replace this with your feature.</p>
</div>
</Paywall>Replace the sample content with your feature. The surrounding paywall continues to show pricing to free users and product access to entitled users.
A few things you can adjust as you go
- Different screen —
<Paywall>is a normal component; use it in the side panel or options instead, or in more than one place. - Your own plan name — "pro" is just a label. Rename it in your checkout call (
metadata: { chromeship_entitlement: "premium" }) and in<Paywall plan="premium">, keeping both in sync. - More than one paid feature — give each one its own plan name and its own Stripe price. A user can own one, the other, or both.
- Subscriptions — add a recurring price in Stripe next to your one-time price. Both appear on the paywall automatically; nothing to branch on in code.
Protect the feature on the server
The popup does not grant access. Stripe sends a signed event to your webhook, and the backend updates the entitlement. Use frontend checks to choose what to display, but verify access again inside any function that performs valuable work.
Refunds, cancellations, renewals, and failed payments update access through the same webhook. The customer portal lets users manage billing without a custom settings flow.
When it's time to ship
Add production Stripe and Supabase values in Environment, use Deploy database, deploy the functions, then point Stripe's live webhook at the deployed stripe-webhook URL. The extension build automatically uses production public values.
