BillingWebhookKit

Stripe webhook order of events in Next.js

Stripe webhooks should be treated as an eventually consistent event stream. Build the route so checkout, invoice, subscription, refund, and duplicate replay events can arrive in an inconvenient order without granting the wrong access.

Generate Stripe fixtures Build sequencing test plan

Common event order you may see

Do not hardcode this as a guarantee. Use it as a fixture set to prove your Next.js route does not depend on one lucky delivery sequence.

  1. checkout.session.completed creates or confirms the customer checkout context.
  2. customer.subscription.created or customer.subscription.updated records subscription lifecycle state.
  3. invoice.paid proves a subscription invoice is paid and can extend access once.
  4. invoice.payment_failed records a failed renewal or dunning path without deleting paid access too early.
  5. charge.refunded, refund.created, or disputes trigger rollback decisions after support review.

Design for out-of-order delivery

Persist first, decide second

Store the provider event ID, object ID, type, raw-body hash, and received timestamp before running billing side effects.

Use pending records

If an invoice arrives before your local subscription row exists, create a pending provider object instead of failing permanently.

Key side effects by object

Use keys such as stripe:invoice:in_123:grant so duplicate delivery cannot extend the same invoice twice.

Make state monotonic

A stale event should not downgrade newer paid state. Compare provider timestamps and local decision state before changing access.

Next.js handler shape

Keep raw-body verification separate from event sequencing logic. The route should accept a valid event, store it, then reconcile access based on durable provider objects.

export async function POST(request: Request) {
  const rawBody = await request.text();
  const signature = request.headers.get("Stripe-Signature");
  const event = verifyStripeEvent(rawBody, signature);

  await recordProviderEvent({
    provider: "stripe",
    eventId: event.id,
    eventType: event.type,
    objectId: event.data.object.id,
    rawBodyHash: sha256(rawBody)
  });

  await reconcileStripeBillingObject(event.data.object);

  return new Response("ok", { status: 200 });
}

Sequencing tests to add

FAQ

Does Stripe guarantee webhook event order?

No. A Stripe integration should tolerate events arriving out of order, duplicated, or retried. The handler should verify signatures, persist provider objects, and make entitlement decisions idempotently.

Which Stripe event should grant access after checkout?

Use the event that proves the product is paid for your business model. For one-time checkout this may be checkout.session.completed plus paid status checks. For subscriptions, invoice.paid often extends access, while subscription events update lifecycle state.

Turn event sequencing into release evidence

The free tools generate Stripe fixtures and test plans for sequencing checks. The CNY 69 Pro Kit is for teams that want copy-ready fixtures, duplicate replay tests, entitlement matrices, and review templates already packaged.