JAVASCRIPT
Secure Webhook Receiver with HMAC Signature Verification
Implement a secure webhook endpoint in Node.js, verifying incoming requests using HMAC signatures to ensure data integrity and authenticity.
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
// IMPORTANT: Use the raw body for signature verification.
// body-parser.json() will parse the body, making it hard to verify raw.
// We need the raw body as a string or buffer.
app.use(bodyParser.json({
verify: (req, res, buf) => {
req.rawBody = buf; // Attach the raw buffer to the request object
}
}));
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'your_super_secret_key'; // Use a strong, unique secret
/**
* Verifies the HMAC signature of an incoming webhook request.
* @param {string} signatureHeader The signature provided in the request header (e.g., 'X-Hub-Signature' or 'stripe-signature').
* @param {Buffer} rawBody The raw request body as a Buffer.
* @param {string} secret The shared secret key for HMAC calculation.
* @param {string} algorithm The HMAC algorithm (e.g., 'sha256').
* @returns {boolean} True if the signature is valid, false otherwise.
*/
function verifyHmacSignature(signatureHeader, rawBody, secret, algorithm = 'sha256') {
if (!signatureHeader || !rawBody) {
return false;
}
// Extract the signature value (e.g., 'sha256=abcdef...' -> 'abcdef...')
// This might vary based on the provider (e.g., GitHub, Stripe)
const parts = signatureHeader.split('=');
if (parts.length < 2) return false;
const signature = parts[1]; // Assuming format is algo=signature
const hmac = crypto.createHmac(algorithm, secret);
const digest = hmac.update(rawBody).digest('hex');
// Use crypto.timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
}
app.post('/webhook/github', (req, res) => {
const githubSignature = req.headers['x-hub-signature-256']; // GitHub uses this header
const isValid = verifyHmacSignature(githubSignature, req.rawBody, WEBHOOK_SECRET, 'sha256');
if (!isValid) {
console.warn('GitHub Webhook: Invalid signature received!');
return res.status(401).send('Unauthorized: Invalid signature');
}
console.log('GitHub Webhook: Signature verified!');
// Process the webhook payload here
console.log('Payload:', req.body);
res.status(200).send('Webhook received and verified');
});
app.post('/webhook/stripe', (req, res) => {
// Stripe's signature verification is a bit more complex, involving timestamps
// For simplicity, this example shows basic HMAC, but real Stripe would use their SDK.
// E.g., const signature = req.headers['stripe-signature'];
// const event = stripe.webhooks.constructEvent(req.rawBody, signature, endpointSecret);
// This example assumes a simple 'X-Stripe-Signature' header with just HMAC-SHA256
const stripeSignature = req.headers['x-stripe-signature'];
const isValid = verifyHmacSignature(stripeSignature, req.rawBody, WEBHOOK_SECRET, 'sha256');
if (!isValid) {
console.warn('Stripe Webhook: Invalid signature received!');
return res.status(401).send('Unauthorized: Invalid signature');
}
console.log('Stripe Webhook: Signature verified!');
// Process the webhook payload here
console.log('Payload:', req.body);
res.status(200).send('Webhook received and verified');
});
app.listen(port, () => {
console.log(`Webhook server listening at http://localhost:${port}`);
console.log('Ensure WEBHOOK_SECRET environment variable is set.');
});
// To test with curl (replace secret and payload):
// const secret = 'your_super_secret_key';
// const payload = JSON.stringify({ "event": "test", "data": { "id": "123" } });
// const hmac = crypto.createHmac('sha256', secret).update(payload).digest('hex');
// curl -X POST -H "Content-Type: application/json" \
// -H "X-Hub-Signature-256: sha256=${hmac}" \
// -d '${payload}' http://localhost:3000/webhook/github
How it works: This Node.js (Express) snippet sets up a secure webhook receiver. When integrating with third-party services that send webhooks, it's crucial to verify the authenticity and integrity of the incoming data. This code uses HMAC (Hash-based Message Authentication Code) signature verification: it computes an expected signature from the request's raw body and a shared secret, then compares it with the signature provided in the request headers. This ensures that the webhook originated from the legitimate sender and that its payload hasn't been tampered with.