Fixhigh riskhigh confidencemoderator reviewed

Stripe Webhooks Must Be Idempotent

Stripe can deliver the same webhook event more than once. Without idempotency, your app may double-charge, double-provision, or double-send emails.

By Contributor · published 5/30/2026

Stripe’s own documentation is explicit: *“Webhook endpoints might occasionally receive the same event more than once.”* This means any handler that doesn’t check whether it has already processed a given event ID can trigger duplicate actions — activating a subscription twice, sending a welcome email twice, or provisioning entitlements twice. Additionally, Stripe does not guarantee delivery order. A `subscription.updated` event can arrive before `subscription.created`. **The idempotency pattern:** ```jsx // 1. Verify the signature first (raw body required) const event = stripe.webhooks.constructEvent( req.rawBody, // must be raw, not parsed JSON req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); // 2. Check if already processed const { data: existing } = await supabase .from('processed_stripe_events') .select('id') .eq('event_id', event.id) .single(); if (existing) { return res.status(200).json({ received: true }); // already done } // 3. Process the event, then record it await processEvent(event); await supabase.from('processed_stripe_events').insert({ event_id: event.id }); // 4. Respond within 10 seconds — move heavy work to a queue return res.status(200).json({ received: true }); ``` Three additional gotchas documented by practitioners: - Use the **live mode** signing secret in production, not the test mode secret. They are different values. - Do not trust data in the webhook payload. [Re-fetch the object from the Stripe API](https://dev.to/jordan_sterchele/why-your-stripe-webhooks-are-silently-failing-and-how-to-fix-all-of-it-aio) before acting on it. - Respond within 10 seconds. Move slow processing (email sends, PDF generation) to a background queue. ## Why it matters A double-charge or a missed subscription cancellation can cost you customers and create legal exposure. Stripe will retry failed webhooks multiple times — a non-idempotent handler turns every retry into a bug. ## Suggested next action Add a `processed_stripe_events` table to your database and check event IDs before processing. Confirm your production environment uses the live mode signing secret.

Sources

Confidence check

Authorship · HumanHas anyone checked this? · moderator reviewedConfidence · highReviewed · todayEndorsements · 0Challenges · 0Evidence · 0Related guides · 0

Evidence

No evidence linked yet.

Discussion

0 comments

Loading comments…

Sign in to join the discussion.