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.