A launch-day story with a clean Stripe charge, a webhook failing on a single config flag, and a security hole we found while fixing it. If you sell software through Supabase, the last section is the part worth stealing.
Published 21 August 2026. The events below happened on GhostNote’s launch day in May 2026.
GhostNote’s first paying customer arrived four hours after we flipped the switch. Stripe showed his $9.99 subscription processed cleanly — money moved, receipt sent, everything a founder refreshes a dashboard hoping to see. The app, meanwhile, kept him on the free tier as if nothing had happened. He had paid, and GhostNote gave him nothing.
The plan upgrade happens in a Supabase Edge Function that listens for Stripe’s webhooks. That function was deployed with verify_jwt = true — a perfectly sensible default for endpoints your own signed-in users call, and exactly wrong for a webhook. Stripe’s servers don’t carry a Supabase JWT, so every event Stripe sent was rejected with a 401 before a single line of our code ran. The checkout worked, the payment worked, and the one message that would have upgraded the account was bounced at the door by our own authentication.
The fix was one line: turn off JWT verification for that endpoint and authenticate the webhook the way Stripe intends — by verifying Stripe’s own signature on the payload. Thirty minutes of launch-day log reading for a one-line diff.
That still left a customer who had paid and received nothing for half an hour. Two options: refund him and ask him to try again, or make it right unilaterally. We comped him a lifetime license as customer number one. He’s still around.
Reading the same code in a panic surfaced something worse than the bug we were looking for: a row-level security gap that would have let any logged-in user update the plan column on their own row — effectively plan='lifetime' for free, no Stripe involved. It had never been exploited, but it didn’t need a webhook or a payment to be a problem. We closed it the same evening.
verify_jwt on them and verify the provider’s signature instead. The 401 happens before your code runs, so no log you wrote will show you why.GhostNote is an invisible AI overlay for Windows and macOS — AI on your screen that stays out of supported screen shares and recordings. The free tier needs no card, which, after this story, you’ll understand is also how we prefer to start.