You built an entire SaaS in a weekend. Cursor, Copilot, or Claude wrote most of the code. It works, it's deployed, users are signing up. That's genuinely impressive. But there's one thing AI code generators almost always get wrong: webhooks.
This isn't about AI-generated code being bad. It's about a specific blind spot. When you ask an AI to "add a Stripe webhook endpoint" or "handle Shopify order notifications," you get code that works in the happy path. But webhooks aren't a happy-path problem — they're an infrastructure reliability problem. And AI tools don't think about infrastructure reliability unless you explicitly ask.
What AI Actually Generates for Webhooks
Here's what you typically get when you ask Cursor or Copilot to add a webhook handler:
// AI-generated webhook handler (typical output)
app.post('/webhooks/stripe', async (req, res) => {
const event = req.body;
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
await db.users.update({
where: { email: session.customer_email },
data: { plan: 'pro', stripeId: session.customer },
});
}
res.json({ received: true });
});
This code works. It handles the event, updates the database, returns 200. Ship it.
But here's what's missing:
-
No signature verification. Anyone can POST to your webhook URL and fake a payment event. AI tools rarely add
stripe.webhooks.constructEvent()unless you specifically ask. - No retry handling. If your database is slow or your server restarts during a deploy, the webhook fails. There's no retry logic.
- No idempotency. Stripe may deliver the same event twice. Without deduplication, you'll grant access twice, send two confirmation emails, or double-charge.
-
No error handling for the webhook itself. If
db.users.update()throws, the entire handler crashes. Stripe gets a 500 and retries, but your code will crash again the same way. - No dead letter queue. After Stripe gives up retrying (3 days), the event is gone. Your customer paid but never got access. You have no record of the failure.
- No monitoring or alerts. You won't know webhooks are failing until a customer emails you.
Why AI Code Generators Miss This
It's not a bug in the AI — it's a natural limitation of how code generation works:
- AI optimizes for the prompt. "Add a Stripe webhook" means: create an endpoint that handles Stripe events. The AI delivers exactly that. It doesn't add infrastructure it wasn't asked for.
- Training data bias. Most webhook tutorials and Stack Overflow answers show the happy path. The AI learned from code that doesn't include retry logic because most example code doesn't either.
- Reliability is cross-cutting. Retry logic, DLQ, monitoring — these aren't features of your webhook handler. They're infrastructure concerns that span your entire system. AI generates code file-by-file, not architecture-by-architecture.
- The failure mode is silent. A missing button is obvious. A webhook that fails during a deploy at 3 AM is invisible. AI can't warn you about problems it can't detect.
Real Failure Scenarios in Vibe-Coded Apps
These are the patterns we see repeatedly in apps built with AI assistance:
The Deploy Gap
You push a new version. Your server restarts. During those 10-30 seconds, Stripe sends a payment_intent.succeeded event. Your server is down. Stripe retries a few times, then gives up after 3 days. The customer paid $99 but never got access to your product.
The Unhandled Exception
Your AI-generated handler works for checkout.session.completed but crashes on customer.subscription.updated because the payload structure is slightly different. Every subscription update webhook returns a 500. Stripe retries 16 times over 3 days, then stops. You don't find out until users report they can't cancel.
The Spoofed Payment
Without signature verification, someone discovers your webhook URL (it's usually predictable — /webhooks/stripe) and sends a fake checkout.session.completed event. Your code grants them Pro access without payment.
The Silent Drift
Your app works fine for weeks. Then you add a feature that makes the webhook handler 200ms slower. Then another. Eventually it times out under load. Stripe starts retrying. You don't notice because there are no alerts. By the time a customer reports a problem, dozens of events have been lost.
What Proper Webhook Handling Actually Looks Like
Here's the full version of what your webhook code should do:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
app.post('/webhooks/stripe',
express.raw({ type: 'application/json' }), // Raw body for signature verification
async (req, res) => {
// 1. Verify signature (reject spoofed events)
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
req.headers['stripe-signature'],
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error('Signature verification failed:', err.message);
return res.status(400).send('Invalid signature');
}
// 2. Check idempotency (skip duplicates)
const alreadyProcessed = await db.webhookEvents.findUnique({
where: { eventId: event.id }
});
if (alreadyProcessed) {
return res.json({ received: true, duplicate: true });
}
// 3. Store the event (so you have a record even if processing fails)
await db.webhookEvents.create({
data: { eventId: event.id, type: event.type, payload: event }
});
// 4. Acknowledge fast — return 200 BEFORE processing
res.json({ received: true });
// 5. Process asynchronously (failures won't affect the response)
try {
await processWebhookEvent(event);
await db.webhookEvents.update({
where: { eventId: event.id },
data: { status: 'processed' }
});
} catch (err) {
console.error('Processing failed:', err);
await db.webhookEvents.update({
where: { eventId: event.id },
data: { status: 'failed', error: err.message }
});
// TODO: alert, retry, or add to DLQ
}
}
);
That's a lot more code. And it still doesn't include retries, a dead letter queue, monitoring dashboards, or alerting. For a solo developer or small team shipping fast, building all of this is a week of work that pulls you away from your actual product.
The Shortcut: Add a Reliability Layer in 2 Minutes
Instead of building all that infrastructure yourself, you can put a reliability layer between the webhook provider and your server. This is exactly what EventDock does.
Here's the setup:
# 1. Sign up at dashboard.eventdock.app (free, no credit card)
# 2. Create an endpoint:
# Provider: Stripe (or Shopify, GitHub, etc.)
# Destination: https://yourapp.com/webhooks/stripe
# → You get: https://in.eventdock.app/ep_abc123
# 3. Point Stripe at your EventDock URL instead of your server:
# Stripe Dashboard → Webhooks → Add endpoint
# URL: https://in.eventdock.app/ep_abc123
# Done. Now you get:
# ✓ Automatic signature verification
# ✓ 7 retries with exponential backoff
# ✓ Dead letter queue with one-click replay
# ✓ Real-time delivery dashboard
# ✓ Slack/email alerts on failures
Your AI-generated webhook handler stays simple — because EventDock handles the reliability part. You focus on business logic; EventDock makes sure the events actually arrive.
Frequently Asked Questions
Do AI code generators add webhook retry logic?
No. AI code generators like Cursor, Copilot, and ChatGPT typically generate webhook handlers that handle the happy path. They rarely add retry logic, dead letter queues, or idempotency handling unless explicitly prompted.
What happens when my server is down and a webhook is sent?
The webhook provider (Stripe, Shopify, etc.) receives an error and retries with exponential backoff. Stripe retries for about 3 days. If your server is still failing after all retries, the event is permanently lost. A reliability layer like EventDock stores events durably and retries independently.
Is vibe coding bad for production apps?
No. AI-assisted development is excellent for building features fast. The gap is in infrastructure reliability — retries, queues, monitoring — which AI tools don't add automatically. The fix isn't to stop using AI; it's to add a reliability layer for critical points like webhooks.
How do I add webhook reliability without rewriting my code?
Use a webhook reliability layer like EventDock. Point your provider at EventDock instead of your server. EventDock handles retries, DLQ, and signature verification. Your existing code doesn't change.
Does Cursor or Copilot add webhook signature verification?
Sometimes, but inconsistently. Specific prompts like "secure Stripe webhook handling" may include it. Default prompts usually don't, leaving your app vulnerable to spoofed events.
Ship fast, don't lose webhooks. EventDock adds retries, DLQ, and monitoring in 2 minutes. Free for 5,000 events/month.
Top comments (0)