webhook signature 🔌 API

Webhook signature verification failed (Stripe, Razorpay, GitHub…)

Your endpoint computed a different HMAC signature than the one the provider sent, so the webhook is rejected.

Seen on: Node.js PHP REST API

Meaning

Providers sign the exact raw request bytes with a shared secret. If your framework parses and re-serialises the JSON before you verify, or you use the wrong secret, the signatures won’t match — even though the payload looks identical.

Common causes

  • Verifying a parsed/re-encoded body instead of the raw bytes (Express json() middleware, Laravel request input)
  • Wrong secret: API key used instead of the webhook signing secret, or test vs live secret
  • Different secret per webhook endpoint in the provider dashboard
  • Timestamp tolerance exceeded (server clock skew or replayed event)
  • Proxy modifying the body (encoding, whitespace)

⚡ Quick fix

  1. Read the raw body before any JSON parsing and verify that
  2. Copy the signing secret of *this* webhook endpoint (and environment)
  3. Compare with a constant-time function
  4. Sync the server clock (NTP)

Detailed fix by platform

Node.js

  1. Express: raw body only on the webhook route:
    javascript
    app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
      const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET);
      res.sendStatus(200);
    });
    app.use(express.json()); // register JSON parsing AFTER the webhook route

PHP

  1. Generic HMAC check on the raw body:
    php
    $raw = file_get_contents('php://input');
    $expected = hash_hmac('sha256', $raw, $webhookSecret);
    if (!hash_equals($expected, $_SERVER['HTTP_X_RAZORPAY_SIGNATURE'] ?? '')) {
        http_response_code(400);
        exit;
    }

How to diagnose

  1. Body — Are you hashing the exact raw bytes?
  2. Secret — Right endpoint secret and environment?
  3. Header — Reading the correct signature header?
  4. Time — Clock in sync / tolerance?

🧠 Still stuck? Analyze your error

Paste the full message, response headers or stack trace — we'll detect the platform and point to the most likely cause.