← Back to all snippets
JAVASCRIPT

Verifying Webhook Signatures with HMAC

Secure your Node.js application's webhooks by verifying incoming request signatures using HMAC, preventing spoofing and ensuring data integrity and authenticity.

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

const app = express();
const PORT = process.env.PORT || 3000;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'your_secret_key'; // Replace with a strong, secret key

// Middleware to parse raw body for signature verification
const rawBodyMiddleware = (req, res, next) => {
    if (req.headers['content-type'] === 'application/json') {
        let data = '';
        req.setEncoding('utf8');
        req.on('data', chunk => { data += chunk; });
        req.on('end', () => {
            req.rawBody = data;
            try {
                req.body = JSON.parse(data);
            } catch (e) {
                return res.status(400).send('Invalid JSON payload');
            }
            next();
        });
    } else {
        next();
    }
};

// Webhook signature verification middleware
const verifyWebhookSignature = (req, res, next) => {
    const signature = req.headers['x-hub-signature'] || req.headers['x-signature']; // Common headers
    if (!signature) {
        console.warn('Webhook received without signature.');
        return res.status(401).send('Unauthorized: No signature provided.');
    }

    if (!req.rawBody) {
        // This case should be handled by rawBodyMiddleware earlier if content-type is json
        // For other content types, you'd need to adjust rawBodyMiddleware
        console.error('Raw body not available for signature verification.');
        return res.status(500).send('Internal Server Error: Raw body missing.');
    }

    const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
    hmac.update(req.rawBody);
    const expectedSignature = 'sha256=' + hmac.digest('hex'); // 'sha256=' prefix is common

    if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
        next();
    } else {
        console.warn('Webhook signature mismatch.');
        return res.status(403).send('Forbidden: Invalid signature.');
    }
};

app.use(rawBodyMiddleware); // Must be before bodyParser.json() if you use it for other routes
// app.use(bodyParser.json()); // Only if you need JSON parsing after signature verification for other routes

app.post('/webhook', verifyWebhookSignature, (req, res) => {
    console.log('Received verified webhook payload:', req.body);
    // Process the webhook payload here
    res.status(200).send('Webhook received and verified.');
});

app.listen(PORT, () => {
    console.log(`Webhook server listening on port ${PORT}`);
});
How it works: This Node.js snippet, using Express, demonstrates how to secure webhook endpoints by verifying the incoming request's signature. Many services send a cryptographic signature (e.g., HMAC-SHA256) in a header (like `X-Hub-Signature`) generated from the raw request body and a shared secret key. The `rawBodyMiddleware` ensures the raw request payload is available, then `verifyWebhookSignature` recalculates the expected signature. By comparing it with the provided signature using `crypto.timingSafeEqual` (to prevent timing attacks), the application can confirm the payload's integrity and origin, rejecting any tampered or unauthorized requests.

Need help integrating this into your project?

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

Hire DigitalCodeLabs