Webhook Signature Validation Guide

Complete guide to webhooks, debugging, and real-time event monitoring with HookMetry

Signature Validation

Signature validation ensures that webhooks are authentic and haven't been tampered with. It prevents unauthorized parties from sending fake webhooks to your endpoint.

How Signature Validation Works

1

Shared Secret

Both you and the webhook provider share a secret key (never transmitted with webhooks)

2

HMAC Generation

Provider creates an HMAC hash of the payload using the secret key (typically SHA-256)

3

Signature Sent

The signature is included in a header (e.g., X-Webhook-Signature)

4

You Verify

Compute the same HMAC with your secret and compare - if they match, the webhook is authentic

Universal HMAC Verification Pattern (Node.js)

This is the pattern used by Stripe, GitHub, Shopify, Razorpay and most providers. The secret key and encoding (hex vs base64) differs per provider — the structure does not.

const crypto = require('crypto');
const express = require('express');
const app = express();

// CRITICAL: Use express.raw() — NOT express.json() — for webhook routes.
// Once JSON middleware parses the body, the raw bytes are gone.
// All major providers (Stripe, GitHub, Razorpay) sign the raw bytes.
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const receivedSig = req.headers['x-webhook-signature'];

  // req.body is a Buffer here — exactly what we need
  const expectedSig = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(req.body)               // ← raw Buffer, NOT JSON.stringify(req.body)
    .digest('hex');                 // or 'base64' — depends on the provider

  // Timing-safe comparison — prevents timing attacks
  const sigBuffer = Buffer.from(receivedSig, 'hex');
  const expBuffer = Buffer.from(expectedSig, 'hex');

  if (sigBuffer.length !== expBuffer.length ||
      !crypto.timingSafeEqual(sigBuffer, expBuffer)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Now safe to parse JSON
  const event = JSON.parse(req.body);
  console.log('Verified event:', event.type);
  res.status(200).json({ received: true });
});

// ⚠️ Common mistake: using express.json() globally and then trying to
// reconstruct the raw body with JSON.stringify(req.body) — this WILL fail
// because JSON.stringify reorders keys and drops whitespace.

Provider Encoding Reference

ProviderHeaderEncodingStrip Prefix?
StripeStripe-Signaturehex (via SDK)Use SDK only
GitHubX-Hub-Signature-256hexYes — strip "sha256="
ShopifyX-Shopify-Hmac-SHA256base64No prefix
RazorpayX-Razorpay-SignaturehexNo prefix
Svix / Clerkwebhook-signaturebase64Strip "whsec_" from secret
TwilioX-Twilio-Signaturebase64 (URL+params)Use SDK — URL matters

Security Benefits

  • • Prevents replay attacks
  • • Ensures data integrity
  • • Authenticates sender
  • • Detects tampering

Without Validation

  • • Anyone can send fake events
  • • Data can be modified
  • • Security vulnerabilities
  • • Potential financial loss

HookMetry Advantage:

HookMetry automatically validates signatures for Stripe, GitHub, and custom HMAC webhooks. You can see validation results in real-time, making debugging authentication issues effortless.

Was this page helpful?

Your feedback helps us improve the docs.