The problem
Our subscription box client discovered customers were receiving $89/month premium boxes without being charged. The accounting team found $127,000 in unprocessed payments over 3 months. Some savvy customers had figured out they could spam-click the checkout button and receive multiple shipments while only being charged once. The fulfillment center was shipping orders marked as "paid" but Stripe showed no corresponding charges.
How AI created this issue
The developer asked ChatGPT to "implement Stripe webhook handling for order fulfillment". ChatGPT generated this problematic code:
// ChatGPT's webhook handler
app.post('/webhook/stripe', async (req, res) => {
const event = req.body;
if (event.type === 'payment_intent.succeeded') {
const paymentIntent = event.data.object;
// Find order by payment intent
const order = await Order.findOne({
stripePaymentId: paymentIntent.id
});
if (order && order.status === 'pending') {
// Mark order as paid and trigger fulfillment
order.status = 'paid';
order.paidAt = new Date();
await order.save();
// Send to fulfillment
await sendToWarehouse(order);
res.json({ received: true });
}
}
res.status(200).send();
});
The critical flaw? No idempotency handling. Stripe can send the same webhook multiple times (for reliability), and network issues can cause duplicate deliveries. Each duplicate webhook marked the order as "paid" again and triggered another shipment. ChatGPT didn't mention webhook signature verification, replay attacks, or the need for idempotency keys.
The solution
- Webhook signature verification: Implemented Stripe's signature verification to prevent forged webhooks:
// Secure webhook implementation const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); app.post('/webhook/stripe', express.raw({ type: 'application/json' }), async (req, res) => { const sig = req.headers['stripe-signature']; let event; try { // Verify webhook signature event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.error('Webhook signature verification failed:', err); return res.status(400).send(`Webhook Error: ${err.message}`); } // Idempotency key from Stripe event ID const idempotencyKey = event.id; // Check if we've already processed this event const processed = await WebhookEvent.findOne({ stripeEventId: idempotencyKey }); if (processed) { console.log('Duplicate webhook received:', idempotencyKey); return res.json({ received: true, duplicate: true }); } // Process the event try { await processStripeEvent(event); // Record that we processed this event await WebhookEvent.create({ stripeEventId: idempotencyKey, type: event.type, processedAt: new Date() }); res.json({ received: true }); } catch (error) { console.error('Error processing webhook:', error); res.status(500).send('Webhook processing failed'); } });
- Payment reconciliation system: Built automated reconciliation to catch mismatches between orders and payments
- Order fulfillment locks: Implemented database-level constraints to prevent duplicate shipments
- Webhook retry handling: Added exponential backoff and dead letter queue for failed webhooks
- Real-time alerting: Set up PagerDuty alerts for payment/order mismatches exceeding $500
The results
- Recovered $127,000 in missed payments through retroactive billing
- Zero duplicate shipments after implementing idempotency
- 99.98% payment accuracy (up from 87%)
- Reduced chargebacks by 94% as customers couldn't exploit the system
- Saved $23,000/month in lost inventory and shipping costs
- Webhook processing time improved 3x with proper async handling
The client learned that payment processing requires battle-tested patterns, not just basic webhook handlers. They now maintain a comprehensive test suite that simulates Stripe's webhook retry behavior, network failures, and race conditions.
Ready to fix your codebase?
Let us analyze your application and resolve these issues before they impact your users.
Get Diagnostic Assessment →