Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2312 min read

I cancelled within 14 days and got two refunds for £99 — Stripe webhook retry + a missing Idempotency-Key on the outbound POST

Sophie (39), runs Common Loaf café in Manchester's Northern Quarter, cancels her Diamond subscription 12 days after upgrade. Sunday she gets a £99 refund. Wednesday her wallet app pings with ANOTHER £99 refund. One cancellation, two Refund objects four days apart. Forensic: Stripe webhook handler chain's steps (4) D1 sync or (5) cache-purge fetch failed transiently → handler returned 500 → Stripe retried the webhook 1 second later → INSIDE the chain `issueRefund()` posted to Stripe `/refunds` a second time → outbound POST had no Idempotency-Key → Stripe created a second Refund object. **PR #661 batch XI F2** three-layer fix: (1) local `stripeApi()` helper accepts `opts.idempotencyKey`, (2) `issueRefund()` now requires `eventId: string` parameter (compile-time enforcement), (3) `stripeClaimKey(eventId, "refund")` derives a deterministic 32-char key → Stripe dedupes server-side for 24h. Pattern: every outbound state-changing POST must carry an Idempotency-Key derived from a stable upstream identifier.

th

thMenu Team

thmenu.com

Found this helpful? Share it.