
Production-grade Stripe integration: webhook signature verification, Redis SETNX idempotency guard against duplicate event delivery, and payment intent creation with exponential-backoff retry. 29 tests covering exactly-once semantics, non-retryable 4xx errors, and concurrent duplicate suppression.
29 tests
Test Suite
idempotency (9) + webhook (8) + payments (5) + retry (6) + app (5)
SETNX
Idempotency
Exactly-once delivery — same Stripe event processed at most once in 24h
4 attempts
Retry Policy
Exponential backoff + jitter, non-retryable 4xx bypassed immediately
Stripe delivers webhooks at-least-once — the same payment_intent.succeeded event can arrive 3–5 times during retries. Without an idempotency guard, each delivery triggers a fulfillment action (email, inventory update, DB write), causing duplicate orders and corrupted state.
Redis SETNX on the event ID: the first delivery writes the key and processes. All subsequent deliveries hit the existing key and return 200 immediately without reprocessing. 24-hour TTL covers Stripe's full retry window. Payment intent creation uses a caller-supplied idempotency key, so double-clicks or network retries never create duplicate charges.
Core Engineering Achievements:
"Express app with route-level middleware separation: raw body parsing only on /webhook (required for Stripe signature verification), JSON parsing everywhere else. Idempotency and retry are separate, independently testable modules — each with its own test file."
Internal Design Doc
Stripe retries every webhook until it gets a 2xx. The only reason duplicate deliveries stay harmless is the eight lines of SETNX that sit between HMAC verification and the business handler. Here is what that looks like on the wire.
Walkthrough
Trace the keys a real duplicate delivery produces. The store starts empty, the first webhook writes one key with a 24-hour TTL, and every subsequent retry of the same event bounces off that key.
Before first delivery
t=0.000sredis> KEYS stripe:event:* (empty array) redis> GET stripe:event:evt_abc123 (nil)
The store has never seen this event id.
After first delivery
t=0.041sredis> SETNX stripe:event:evt_abc123 1 (integer) 1 redis> EXPIRE stripe:event:evt_abc123 86400 (integer) 1 redis> TTL stripe:event:evt_abc123 (integer) 86400
Key claimed. Handler runs, order fulfilled, 200 returned.
Duplicate retry arrives
same evt_abc123, new delivery attemptredis> SETNX stripe:event:evt_abc123 1 (integer) 0 // already exists — we bail redis> TTL stripe:event:evt_abc123 (integer) 86398 // same key, ~2s burned
Why the handler is skipped: SETNX returning 0 is our signal that this exact event id has been processed in the last 24 hours. We short-circuit, return 200, and Stripe stops retrying. No duplicate charge reconciliation, no double email, no phantom inventory decrement.
The 4xx retry trap
Retrying on transient failure is correct. Retrying on a Stripe 4xx is not — it turns a single declined card into four dashboard failures and four operator tickets.
Naive retry (wrong)
anti-patternfor (let i = 0; i < 4; i++) {
try {
return await stripe.paymentIntents
.create(params);
} catch (err) {
// Retry everything. What could go wrong?
await sleep(backoff(i));
}
}
throw new Error("out of retries");What we do
productionasync function withRetry(fn, { tries = 4 } = {}) {
for (let i = 0; i < tries; i++) {
try {
return await fn();
} catch (err) {
const code = err?.statusCode ?? 0;
// 4xx: Stripe said no. Stop.
if (code >= 400 && code < 500) throw err;
// 5xx or network: retry with backoff.
if (i === tries - 1) throw err;
await sleep(250 * 2 ** i);
}
}
}Idempotency-Key header so retries are safe.The decision: trust Stripe's 4xx responses. They already thought about it.
I'm always open to discussing technical implementations, from state management strategies to infrastructure scaling.