Audience: Developers, Security
Outcomes: Safe, idempotent reconciliation across providers
Stripe webhook (Express, raw body)
import express from 'express'; import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); const app = express(); app.post('/api/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => { try { const sig = req.header('Stripe-Signature')!; const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET! ); if (hasProcessed(event.id)) return res.json({ received: true }); switch (event.type) { case 'payment_intent.succeeded': onPaymentSucceeded(event.data.object); break; case 'payment_intent.payment_failed': onPaymentFailed(event.data.object); break; case 'charge.refunded': onRefunded(event.data.object); break; default: log('Unhandled', event.type); } rememberProcessed(event.id); res.json({ received: true }); } catch { res.status(400).send('Signature verification failed'); } } );
Rules
Use raw body (not parsed JSON)
Deduplicate by
event.id
Least-privilege keys; rotate regularly
QA checklist
Tampered payload ⇒ 400
Duplicate event processed once
Bank/open-banking HMAC webhook
import crypto from 'crypto'; import express from 'express'; const app = express(); function verifyHmac(raw: Buffer, signature: string, secret: string) { const mac = crypto.createHmac('sha256', secret).update(raw).digest('hex'); return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(signature)); } app.post('/api/webhooks/bank', express.raw({ type: '*/*' }), (req, res) => { const sig = String(req.header('X-Hmac-Signature') || ''); if (!verifyHmac(req.body as Buffer, sig, process.env.BANK_WEBHOOK_SECRET!)) return res.status(400).send('Bad signature'); const event = JSON.parse((req.body as Buffer).toString()); if (hasProcessed(event.id)) return res.json({ received: true }); handleBankEvent(event); rememberProcessed(event.id); res.json({ received: true }); });
QA checklist
Wrong secret ⇒ 400
Replay ignored via idempotency
Runbook: “Webhook gap/lag”
Alert if gap > X min; replay from provider dashboard; verify dedupe