
Went through my Stripe webhook handler today, prompted by a conversation about proving system behavior instead of assuming it. Found something, then found I'd described it wrong on the first pass — which turned out to be the more interesting part.
My first read: "the handler syncs subscription state directly from the webhook payload, never re-fetches from Stripe's API." Sounded clean. It was wrong.
Actual behavior, once I checked the code properly: it's a mixed pattern. checkout.session.completed and invoice.paid events call stripe.subscriptions.retrieve() to pull fresh state from Stripe before syncing — genuinely verified against the live source. But customer.subscription.updated and customer.subscription.created sync directly from the webhook payload, no re-fetch at all.
So depending on which event triggers the sync, my system either has real evidence the local state matches Stripe's current state, or it doesn't — and I'd flattened that into one blanket claim without checking each path individually.
The lesson isn't really about Stripe webhooks. It's that "I checked this" and "I checked this carefully enough to catch my own oversimplification" are different levels of rigor, and the second one usually takes an extra pass I'm tempted to skip once the first answer sounds plausible.
Anyone else caught themselves doing this — describing a system's behavior with more confidence than the actual code supported?
The second-pass finding is the useful part — “I checked” vs “I checked each path” are different confidence levels.
Same trap shows up on payment_failed: some handlers re-fetch the invoice/subscription before flipping entitlements; others trust the payload and return 200. Both can look fine until a race or a stale event lands. Listing the events that re-fetch vs the ones that don’t (and whether payment_failed is in the re-fetch set) is usually the cheapest next audit.
Curious — after you caught the oversimplification, did payment_failed land in the re-fetch column or the payload-trust column?
This resonates a lot — how long did it take before you saw any real signal on it?
Nice work shipping it. What has been the biggest challenge since launch?
Appreciate the honesty here, most people only share the wins.
Interesting take. Would you still recommend this approach to someone starting today?
What made you pick this stack over the alternatives?