← All posts
webhookssecurityvibe-coded appsAI codingauthentication

How to Secure Your Vibe-Coded App's Webhooks from Abuse

O

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:

  • [ ] Do your webhook endpoints require any authentication? Many vibe-coded apps expose webhooks as public URLs with no auth—anyone can POST fake events.
  • [ ] Are you verifying a signature or token? Most webhook providers (Stripe, GitHub, Slack) send a signature header. If you're not checking it, you're likely vulnerable.
  • [ ] Is the payload validated? Even if you verify the sender, you should validate that the event data matches expected formats and types.
  • [ ] Do you handle replay attacks? If an attacker captures a valid webhook request, can they resend it to trigger duplicate actions (e.g., charge a customer twice)?
  • [ ] Are your secrets stored securely? Hardcoded secrets in your code or commit history are a goldmine for attackers.
  • [ ] Do you have rate limiting on webhook endpoints? Attackers can flood your endpoint to cause denial of service.
  • 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-limit
    import 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:

  • No signature verification at all – The endpoint just processes any POST. This is the #1 mistake.
  • Ignoring the raw body – Many frameworks parse JSON automatically, so the signature check fails because the body is no longer the original string.
  • Hardcoded secrets – AI tools sometimes embed the webhook secret directly in the code, which then gets committed to GitHub.
  • Not checking event types – Even with signature verification, if you don't validate the event type, attackers can send unexpected events that trigger unintended logic.
  • No idempotency – Replaying a webhook can cause duplicate charges, double refunds, or other side effects.
  • 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.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free