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.
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
- Read the raw body before any JSON parsing and verify that
- Copy the signing secret of *this* webhook endpoint (and environment)
- Compare with a constant-time function
- Sync the server clock (NTP)
Detailed fix by platform
Node.js
- 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
- 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
- Body — Are you hashing the exact raw bytes?
- Secret — Right endpoint secret and environment?
- Header — Reading the correct signature header?
- 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.
Was this page helpful?
Report a correction or suggest an improvement
Last updated 2 Oct 2026