Stripe webhook · symptom

Out-of-order events

what it looks like
customer.subscription.updated (active) arrived after customer.subscription.deleted, user got Pro back

What it means

Stripe doesn't guarantee delivery order. A retried older event can land after a newer one, so writing event.data.object blindly can overwrite fresh state with stale state.

Common causes

  • Writing the event payload snapshot straight to the database
  • Assuming created always arrives before updated
  • Retries of an old event

How to fix it

  1. Take only the object id from the event
  2. Re-fetch the subscription with stripe.subscriptions.retrieve(id) and write its current state
  3. Or compare event.created / a stored timestamp and ignore older events
src/lib/billing.ts
case "customer.subscription.updated":
case "customer.subscription.deleted": {
  const sub = await stripe.subscriptions.retrieve(event.data.object.id);
  await db.user.updateMany({
    where: { stripeCustomerId: sub.customer as string },
    data: { subscriptionStatus: sub.status },
  });
  break;
}
verify the fix
stripe listen --forward-to localhost:3000/api/stripe/webhook
# put the whsec_... it prints in .env.local, then restart npm run dev
stripe trigger customer.subscription.updated
stripe events resend evt_...   # the customer.subscription.created event from that run

pass if After the older event is replayed, the stored subscription still matches what the Dashboard shows.

works with stripe-node 23.0.0 · Next.js 16.4.3 App Routerlast verified CLI steps: Stripe CLI 1.55.2, Oct 27, 2026

prevent it · stripe-webhooks.mdc (free Cursor Rules Pack): Re-fetches the object from Stripe instead of trusting event order or event.data.object.

Prompt for Cursor or Claude Code

My Stripe webhook can apply stale subscription state when events arrive out of order. Change the handler to take only the subscription id from the event, re-fetch it with stripe.subscriptions.retrieve, and write its current state. Add a test where updated(active) arrives after deleted.

Not quite your error? Paste yours into the free VibeBoot Doctor or browse the Stripe webhook fixer.

next: skip the next one

Skip the debugging. The VibeBoot SaaS Starter ships with this already handled: a signature-verified webhook (raw body + constructEvent) that re-fetches the subscription from Stripe before saving it, with Vitest tests. Plus auth and a database on Next.js 16.4.3. $49, one-time.

14-day money-back guarantee · lifetime updates

$ fixed it? get the next one first

New fixes for what AI agents break, by email.

When we add a fix or a Cursor rule for a new Next.js, Auth.js, Prisma or Stripe break, it goes out in the VibeBoot newsletter. Free, occasional, unsubscribe in one click.

Subscribe free