NODEJS
Secure Webhook Signature Verification in Node.js
Ensure webhook integrity and authenticity in your Node.js backend by verifying incoming signatures using HMAC, preventing unauthorized data processing.
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();
const WEBHOOK_SECRET = 'your_super_secret_webhook_key'; // Keep this secure!
// Middleware to parse raw body for signature verification
// Must be used BEFORE body-parser.json() or similar, as signature needs raw body
const rawBodyMiddleware = (req, res, next) => {
let data = '';
req.setEncoding('utf8');
req.on('data', chunk => {
data += chunk;
});
req.on('end', () => {
req.rawBody = data;
next();
});
};
app.use(rawBodyMiddleware);
app.use(bodyParser.json()); // Now parse JSON after capturing raw body
// Webhook endpoint
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'] || req.headers['stripe-signature']; // Example header names
const payload = req.rawBody; // Use the raw body for verification
if (!signature || !payload) {
return res.status(400).send('Missing signature or payload.');
}
try {
// Determine algorithm (e.g., 'sha256' for Stripe, 'sha1' for GitHub)
// Some providers prefix it, like 'v1=' for Stripe, so you might need to extract.
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(payload);
const expectedSignature = `sha256=${hmac.digest('hex')}`; // Adjust prefix if needed
// Compare signatures securely using timing-attack resistant comparison
if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
console.log('Webhook signature verified successfully!');
// Process your webhook data
console.log('Webhook data:', req.body);
res.status(200).send('Webhook received and verified.');
} else {
console.warn('Webhook signature verification failed!');
res.status(401).send('Invalid signature.');
}
} catch (error) {
console.error('Error verifying webhook signature:', error);
res.status(500).send('Internal server error during verification.');
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Webhook listener running on port ${PORT}`);
});
How it works: This Node.js Express snippet demonstrates how to securely verify incoming webhook signatures. Many external services (like Stripe, GitHub) send a signature header along with the webhook payload, generated using a shared secret and HMAC. This code uses a custom middleware to capture the raw request body (essential for verification), then calculates its own HMAC signature using the `crypto` module. Finally, it compares this computed signature with the one provided in the header using `crypto.timingSafeEqual`, which prevents timing attacks, ensuring the webhook's authenticity and integrity before processing its data.