Webhook handlers are idempotent
Why it matters
Webhook providers (Stripe, Clerk, GitHub, Svix) retry delivery on any 5xx response — which is the correct behavior for eventual consistency. But a webhook handler without idempotency converts that reliability guarantee into a defect multiplier: a transient 500 during payment.succeeded processing means the payment is recorded twice, the fulfillment email sends twice, and the inventory decrements twice. For Stripe specifically, this can cause double charges in your records, triggering refund reconciliation issues that require manual intervention. Idempotency is not an optional hardening step — it is the contract that makes retries safe.
Severity rationale
Critical because non-idempotent webhook handlers turn provider retry logic into a data corruption vector, causing duplicate charges, duplicate records, and fulfillment errors on every transient 5xx.
Remediation
Use a processedWebhook table with a unique constraint on eventId. An atomic insert that rejects duplicates is the cleanest deduplication primitive — no Redis, no TTL expiry, and the processed set is queryable for debugging.
export async function POST(req: Request) {
const body = await req.text()
const sig = req.headers.get('stripe-signature')!
const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!)
try {
await prisma.processedWebhook.create({ data: { eventId: event.id } })
} catch {
// Unique constraint violation = already processed
return Response.json({ ok: true, duplicate: true })
}
// Side effects only run once
if (event.type === 'payment_intent.succeeded') {
await prisma.payment.create({ data: { ... } })
}
return Response.json({ ok: true })
}
The processedWebhooks table needs only eventId TEXT PRIMARY KEY and createdAt TIMESTAMP. Add a cron to purge rows older than 30 days.
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Hidden Cost Audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- iso-25010:2011 · reliability
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ai-slop-cost-bombs·automated