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.