← Back to all snippets
JAVASCRIPT

Securely Verifying Webhook Signatures with HMAC

Enhance webhook security in Node.js by verifying incoming request signatures using HMAC, protecting your application from spoofed or tampered data payloads.

const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');

const app = express();
const PORT = process.env.PORT || 3000;

// IMPORTANT: Use a strong, unique secret key for your webhooks
// This should be stored securely, e.g., in environment variables.
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'your_super_secret_key';

// Raw body parser is crucial for signature verification as the signature
// is generated over the raw payload, not the parsed JSON.
app.use(bodyParser.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signatureHeader = req.headers['x-hub-signature-256'] || req.headers['x-hub-signature'];
  const payload = req.body.toString('utf8'); // The raw body as a string

  if (!signatureHeader || !payload) {
    return res.status(400).send('Missing signature header or payload');
  }

  const [algorithm, signature] = signatureHeader.split('=');

  // Ensure the algorithm matches what the sender uses (e.g., 'sha256')
  if (algorithm !== 'sha256') {
    return res.status(400).send('Unsupported signature algorithm');
  }

  // Calculate HMAC using the raw payload and your secret
  const hmac = crypto.createHmac(algorithm, WEBHOOK_SECRET);
  hmac.update(payload);
  const digest = hmac.digest('hex');

  // Compare the calculated digest with the received signature
  if (crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature))) {
    try {
      const eventData = JSON.parse(payload); // Parse the payload ONLY after verification
      console.log('Webhook payload verified and parsed:', eventData);
      // Process your webhook event here
      res.status(200).send('Webhook received and verified');
    } catch (error) {
      console.error('Error parsing JSON payload:', error);
      res.status(400).send('Invalid JSON payload');
    }
  } else {
    console.warn('Webhook signature mismatch!');
    res.status(403).send('Webhook signature verification failed');
  }
});

app.listen(PORT, () => {
  console.log(`Webhook listener running on port ${PORT}`);
  console.log(`Secret: ${WEBHOOK_SECRET.length > 10 ? WEBHOOK_SECRET.substring(0, 10) + '...' : WEBHOOK_SECRET}`);
});
How it works: This Node.js snippet demonstrates how to secure webhook endpoints by verifying the signature provided in the request headers. Many API providers (e.g., GitHub, Stripe) send an HMAC signature along with the payload. This signature is generated using a shared secret key and the request body. By calculating the HMAC on your server using the same secret and comparing it with the received signature, you can ensure the webhook payload hasn't been tampered with and originated from the legitimate sender. It's crucial to use a raw body parser (like `bodyParser.raw`) as the signature is calculated over the raw, unparsed request body. The `crypto.timingSafeEqual` function is used for comparison to prevent timing attacks.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs