Short answer
A 200 only means your endpoint received the event. Stripe asks you to answer quickly, before the slow work, so if the database write fails after that, Stripe still records a successful delivery and has nothing to retry. Your own server logs hold the error. Check them first, then the event types your endpoint receives, repeated or out-of-order events, and the database key your handler writes with.
Where the update goes missing
01
The write fails after the 200, and nothing logs it
“Configure your handler to process incoming events with an asynchronous queue.” Stripe, Webhooks
The handler answers Stripe first and does the work afterwards, so an error in the database write never reaches Stripe. Log every handler error and get alerted on it. Stripe recommends an asynchronous queue for handling spikes, and a queue is also a natural place to retry a failed write yourself.
02
The endpoint doesn’t receive the event your code handles
“Select the event types you want to receive.” Stripe, Webhooks
Each endpoint receives only the event types selected for it. If your code acts on checkout.session.completed but the endpoint was set up for other events, what you see delivered isn’t what your code waits for. Stripe’s own example handler returns 200 for event types it doesn’t handle, so a mismatch still looks green. Compare the endpoint’s event list in the Dashboard with your code.
03
Events arrive out of order, or twice
“Stripe doesn't guarantee the delivery of events in the order that they're generated.” Stripe, Webhooks
An invoice.paid event can arrive before the subscription your code expects to update, and the same event can arrive more than once. Record the event IDs you’ve processed and skip repeats, fetch a missing object from the API instead of assuming it exists, and don’t use the created time to order events.
04
The handler works from an old copy of the object
“You can also retrieve the API resource from the Stripe API to access the latest and up-to-date object definition.” Stripe, Webhooks
An event carries the object as it was when the event happened. If your code needs its current state, such as a subscription that has changed since, retrieve it from the API before writing.
05
In a Supabase app: row-level security blocks the write
Check which key the handler writes with. With the publishable or anon key, row-level security applies to it like any visitor, and a policy can stop the update. A handler that runs only on the server can use the secret key, which must never reach the browser.
06
It works in the sandbox but not in live mode
Live mode has its own webhook endpoints and signing secrets. Work through the test-to-live checklist linked below.
Checked against the vendor’s docs on : Stripe docs: Receive Stripe events in your webhook endpoint; Supabase docs: Row Level Security.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- $950Stripe Billing Fix and SetupSubscriptions, webhooks, customer portal and failed payments set up or fixed, tested in Stripe test mode.See scope and start
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
Related: Stripe paid but account not upgraded · Stripe webhook signature error · Stripe works in test mode, not live · Supabase new row violates RLS · Supabase service_role key exposed