Why isn’t my Stripe webhook updating Supabase?
Usually because the webhook points to the wrong URL, fails its signature check, or is blocked by database rules when it writes.
In live mode, Stripe retries a failed webhook for up to three days. Signature checks need the raw request body, so parsing it first makes every event fail.
Source: Stripe docs, Webhooks, read 3 Nov 2026
The usual causes
- The endpoint is registered in a sandbox or test mode only, or still points to a preview URL.
- The signing secret in your environment is from a different endpoint or mode, so every event fails verification.
- The handler parses the body as JSON before verifying it. Stripe signs the raw body, so the check fails.
- The handler is a Supabase Edge Function with JWT verification left on, the default. Stripe sends no Supabase token, so every call gets a 401. Set
verify_jwt = falsefor that function, or deploy it with--no-verify-jwt, and rely on the Stripe signature check instead. - In an Edge Function, the code reads the body with
req.json()or callsconstructEvent. Read it withawait req.text()and verify it withconstructEventAsyncandStripe.createSubtleCryptoProvider(), as in the Supabase example. - The handler writes with the anon key, and row-level security blocks the write.
- The table is new and has no grants. Since 30 October 2026, Supabase no longer grants new tables to the API roles, so the write fails with a 42501 permission denied error, even with the
service_rolekey. - The function times out or returns an error, and nothing logs it.
- The app listens for the wrong event, for example
checkout.session.completedbut notinvoice.paidfor renewals.
How a reliable setup looks
- One endpoint per environment, each with its own signing secret.
- Signature verified on the raw body before anything else.
- Writes done server-side with a key that never reaches the browser.
- Each event ID stored, so a retried event is not processed twice.
- A 2xx response only after the event is safely stored, with slow work moved to a queue so Stripe does not time out.
- A way to compare Stripe and your database, and fix any that disagree.
Where to look first
In the Stripe dashboard, open Workbench, then Webhooks, pick your endpoint and open Event deliveries. Click a failed delivery to see its HTTP status code. A 400 usually means a signature problem, a 401 or 403 an auth problem, a 404 a wrong URL, and a 500 an error in your handler.
Get Badii Boutar’s stories in your inbox
Join Medium for free to get updates from this writer.
In a sandbox, Stripe retries a failed event only three times over a few hours, so a broken test endpoint goes quiet fast. You can resend an event from the dashboard for up to 15 days.
Sources
- Stripe docs, Receive Stripe events in your webhook endpoint, read 3 Nov 2026
- Supabase docs, Function configuration, read 3 Nov 2026
- Supabase example, Stripe webhooks Edge Function, read 3 Nov 2026
- Supabase changelog, Tables not exposed to Data and GraphQL API automatically, read 3 Nov 2026
First published on badii.studio. I review and fix apps built with Lovable, Bolt and other AI tools before launch. If yours has a bug like this, book a Tech Check.









