How to Secure Your Vibe-Coded App's Webhooks from Abuse
OverMCP Team
Quick answer
Webhooks are a common attack vector in AI-built apps because developers often forget to validate that requests actually come from the expected source. To secure your vibe-coded app's webhooks from abuse, you need to verify the sender using a shared secret and signature, validate the payload, and handle replay attacks. This guide walks you through the exact steps, from generating secrets to implementing signature verification, so you can ship fast without leaving your webhooks wide open.
What to check first
Before diving into fixes, run this checklist to see if your webhooks are vulnerable:
If you answered "no" to any, keep reading.
Step-by-step fix
1. Generate a strong secret and store it securely
Your webhook secret should be a long, random string. Generate one using a tool like openssl rand -hex 32 or use your provider's dashboard. Store it in environment variables (e.g., WEBHOOK_SECRET) and never hardcode it.
2. Verify the signature
Most webhook providers sign their requests with an HMAC-SHA256 signature. Here's a typical implementation in Node.js:
// webhook.js
import crypto from 'crypto';
const secret = process.env.WEBHOOK_SECRET;
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).end();
}
const signature = req.headers['x-signature'];
const rawBody = req.rawBody; // Ensure you capture raw body before parsing
if (!signature) {
return res.status(401).json({ error: 'Missing signature' });
}
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the event
const event = JSON.parse(rawBody);
console.log('Webhook received:', event.type);
res.status(200).json({ received: true });
}Important: Use timingSafeEqual to prevent timing attacks. Also, ensure you capture the raw body—most frameworks parse JSON automatically, which breaks signature verification.
3. Validate the payload
After verifying the signature, validate the event type and data. For example, if you expect invoice.paid, reject anything else:
const allowedEvents = ['invoice.paid', 'invoice.failed'];
if (!allowedEvents.includes(event.type)) {
return res.status(400).json({ error: 'Unsupported event type' });
}Also validate required fields: if (!event.data || !event.data.id) return res.status(400).end();
4. Handle replay attacks
Add a timestamp to your webhook payload (most providers do) and check that it's within a few minutes of the current time. Additionally, maintain a cache of processed event IDs to ensure idempotency.
// Using a simple in-memory set (for production, use Redis)
const processedEvents = new Set();
if (processedEvents.has(event.id)) {
return res.status(200).json({ received: true, duplicate: true });
}
processedEvents.add(event.id);5. Add rate limiting
Use a middleware like express-rate-limit to limit requests to your webhook endpoint. This prevents abuse if your signature verification is bypassed.
npm install express-rate-limitimport rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests',
});
app.use('/webhook', limiter);6. Test your implementation
Send test events from your provider's dashboard and ensure your endpoint rejects invalid signatures. You can also use tools like Postman to simulate forged requests.
Common mistakes
AI coding tools often produce webhook endpoints that work but skip security. Here are the most common issues we see in vibe-coded apps:
Why vibe-coded apps are especially vulnerable
When you use AI to generate code, the AI tends to focus on functionality, not security. It will happily create a webhook endpoint that works, but it won't automatically add authentication or signature checks unless you explicitly ask. That's why it's critical to review every external-facing endpoint your AI-built app exposes.
Tools like OverMCP's free AI app security scanner can help you catch missing authentication and other common issues before attackers do. You can also use our security headers checker to ensure your webhook responses are hardened.
Conclusion
Securing webhooks doesn't have to be complicated. By following the steps above—signature verification, payload validation, replay protection, and rate limiting—you can prevent most webhook abuse. Remember to store secrets safely and test your implementation thoroughly.
If you're short on time, use a continuous security monitoring service to keep an eye on your endpoints even after launch. And don't forget to run a secret leak scanner to make sure no secrets ended up in your repo.
Your vibe-coded app can be secure—you just have to give it a little attention.
FAQ
What is a webhook and why is it a security risk?
A webhook is an HTTP callback that sends real-time data to a URL when an event occurs. It's a security risk because if the endpoint doesn't verify the sender, attackers can send fake events to trigger actions like canceling subscriptions or exfiltrating data.
How do I get the raw body in Next.js for signature verification?
In Next.js API routes, you can use req.body but it may already be parsed. To get the raw body, you can use the pages/api route with export const config = { api: { bodyParser: false } } and then read the stream manually. Alternatively, use a middleware like raw-body.
Can I use HTTPS instead of signature verification?
HTTPS encrypts data in transit but doesn't authenticate the sender. An attacker could still send requests to your endpoint if they know the URL. Signature verification is the only reliable way to confirm the webhook is genuine.