RORK LABJP
MAX — Rork Max generates native Swift for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro, with 2-click App Store publishing and no Xcode requiredSTACK — Standard Rork builds cross-platform mobile apps with React Native (Expo); choosing between the two by use case is the key decisionFOCUS — Unlike web-first tools such as Bolt or Lovable, Rork specializes in native iOS and Android app generationBUGS — A hands-on review reports Rork resolved about 70% of bugs without manual help, with the remaining 30% needing edits in the exported codebaseFUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)PRICING — It is free to start, with paid plans from $25/month, so you can try before committingMAX — Rork Max generates native Swift for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro, with 2-click App Store publishing and no Xcode requiredSTACK — Standard Rork builds cross-platform mobile apps with React Native (Expo); choosing between the two by use case is the key decisionFOCUS — Unlike web-first tools such as Bolt or Lovable, Rork specializes in native iOS and Android app generationBUGS — A hands-on review reports Rork resolved about 70% of bugs without manual help, with the remaining 30% needing edits in the exported codebaseFUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)PRICING — It is free to start, with paid plans from $25/month, so you can try before committing
Articles/Dev Tools
Dev Tools/2026-06-16Advanced

Why Your Rork App's Stripe Webhooks Drop Events Only in Production — Field Notes on Idempotency, Retries, and Out-of-Order Delivery

A field-tested four-layer design for stabilizing Stripe webhooks that pass locally but silently misfire in production: signature verification on Workers, event-ID idempotency, fast-2xx-then-process, and a reconciliation job that survives out-of-order delivery. Built around Cloudflare Workers and KV.

Stripe17webhookCloudflare Workers18idempotencypayments5

Premium Article

The payment went through, but the user's access never changed. Stripe's dashboard shows Payment succeeded. Recent deliveries shows 200 OK. And yet the app still treats them as a free user. If you do payments as a solo indie developer, you eventually hit this exact state: everything looks correct, but the numbers don't line up.

The frustrating part is that it passes locally. A single stripe trigger works on the first try, and then production quietly drops events. The obvious failures — a broken signature — surface fast. The ones that eat hours are the failures that still log as success: the same event processed twice, an event that arrived out of order, a handler that ran too slowly and got retried. None of those look like errors in your logs.

These are field notes from stabilizing webhook handling for an app I built with Rork, backed by Cloudflare Workers and KV. I'll keep the signature-verification part short and spend the time on the more interesting question: once the event is in your hands, how does it break?

Why "200 OK but still wrong" happens

The premise to internalize is that Stripe delivers webhooks at least once. That means the same event can arrive more than once. Network jitter, a slow response from your side, a retry on Stripe's side — for any of these reasons, checkout.session.completed can show up twice. It's infrequent, but it absolutely happens.

If your handler assumes "process whatever arrives, every time," the second delivery grants access again or re-runs the subscription-start logic. Without idempotency, that second run is recorded as a perfectly normal success — two 200 OK lines, no error in sight. That's the real identity of most "everything looks right but it's wrong" cases.

There's a second trap: subscription lifecycle events are not ordered. customer.subscription.updated can arrive before customer.subscription.created. If you treat the payload as the timeline of truth and drive a state machine from it, you'll overwrite current state with stale data.

So stabilization is less about signature verification and more about designing a receiver that is resilient to duplicates, to reordering, and to delay.

Layer 1: Verify the signature with the async API on Workers

You still need verification as a foundation. There's one Cloudflare-specific gotcha worth a paragraph. The synchronous constructEvent() used in Node doesn't work in the Workers Web Crypto environment. Switch to the async variant, and always take the body as raw text.

// src/app/api/webhook/route.ts
import Stripe from 'stripe';
import { NextRequest, NextResponse } from 'next/server';
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export async function POST(req: NextRequest) {
  // text(), not json(). Parsing changes the bytes and verification will always fail.
  const body = await req.text();
  const signature = req.headers.get('stripe-signature') ?? '';
 
  let event: Stripe.Event;
  try {
    const cryptoProvider = Stripe.createSubtleCryptoProvider();
    event = await stripe.webhooks.constructEventAsync(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!,
      undefined,
      cryptoProvider,
    );
  } catch (err) {
    // A bad signature is a config mistake, not a transient fault. 400 = don't retry.
    console.error('signature verification failed:', err);
    return NextResponse.json({ error: 'invalid signature' }, { status: 400 });
  }
 
  return handleEvent(event);
}

The thing to remember is to return 400 when verification fails. A signature mismatch is a configuration problem, not a network glitch, so retrying won't fix it. Conversely, for the transient processing failures below, you return 500 to make Stripe retry. This distinction — when to invite a retry and when to give up — is the starting point for retry-storm prevention.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A concrete KV idempotency pattern keyed on event.id that stops double-granting caused by Stripe's at-least-once retries
How to switch to a fast-2xx-then-process handler so slow work never triggers Stripe's retry storm
An operational pattern that stops trusting subscription event payloads and reconciles against a fresh retrieve instead
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-04-26
Usage-Based Billing for Rork AI Apps with Stripe Meter — Charge by API Calls and Tokens, Not Flat Rates
A complete guide to implementing Stripe Meter Billing in your Rork AI app. Covers meter setup, Cloudflare Workers usage reporting, subscription design with free tiers, and error handling — all with working, production-ready code.
Dev Tools2026-04-12
to Building Marketplace App Payments with Rork and Stripe Connect
A comprehensive guide to integrating Stripe Connect into a Rork-built marketplace app. Covers seller account onboarding, destination charges, platform fee distribution, Webhook handling, and launch compliance.
Dev Tools2026-04-07
Rork Max × Stripe: Apple Pay & Google Pay — Boost Purchase Conversion with One-Tap Checkout
A comprehensive guide to implementing Apple Pay and Google Pay in Rork Max using Stripe Payment Sheet. Covers every step from Merchant ID setup and backend development to production release and conversion optimization.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →