Payment Fixes 6/15/2026
Integrations with Stripe is now working again.
Most checkout flows have a quiet assumption baked in: the user will stay on the success page long enough for the software to respond. We broke that assumption and shipped anyway. Here's what we found when we looked closely, and how we fixed it.
THE PROBLEM
When someone pays for an Asix.live subscription, Stripe redirects them to /checkout/success. Our success page showed a confirmation and a green checkmark — and then a button labeled "Confirm Subscriptions."
You had to click the button.
If you closed the tab, navigated away, or lost your connection in those few seconds between "Payment Successful!" and the button click, your subscription was never written to our database. Your card was charged. You got nothing.
We also had a Stripe webhook listening for customer.subscription.created. The webhook was supposed to be the reliable fallback. Except it wasn't, because the first thing our webhook handler did was look for a pre-existing subscription row — and throw an error if it didn't find one. New subscribers have no pre-existing row. Every new-subscriber webhook event was silently failing, retrying, and failing again.
Two independent activation paths. Both broken.
THE FIX
The success page now auto-activates via a useEffect hook the moment the payment status comes back as 'paid'. No button. No waiting. The user sees a spinner that resolves to a list of activated subscriptions.
The webhook was refactored to actually create new rows. When customer.subscription.created fires, we now retrieve the Stripe customer object to get the userId we stored in its metadata during checkout. We map the price ID to a project slug, then upsert — insert on first subscription, update on renewals.
One more thing we noticed while auditing: the old confirm-subscriptions endpoint accepted userId from the POST body without verifying who was asking. A user who knew another user's paid session ID (which appears in the URL on the success page) could POST an arbitrary userId and activate a subscription for someone else's account. We replaced that with JWT verification — the server now reads the caller's Supabase token from the Authorization header and checks it against the session's metadata.
WHAT WE LEARNED
Billing systems are not just Stripe integration. They're a coordination problem between three systems: the browser, the webhook listener, and the database — each of which can fail independently. The safe design is to make the webhook the canonical activation path and treat the success-page confirmation as a faster but unreliable shortcut.
Our webhook should have been able to stand alone from day one. It wasn't, because the function that handled it was written to update existing rows, not create them. A naming choice (updateSubscription instead of upsertSubscription) that quietly described the wrong behavior.
THE AUDIT TRAIL
We caught this during a full monetization health audit — part of our weekly morning routine where we review billing logs, subscription gates, and webhook coverage before touching any feature code. The routine is what found it; the fix took less than two hours.
If you're building subscription billing and you haven't stress-tested what happens when your webhook fails for a brand-new customer, now is a good time.