JAVASCRIPT
Securely Receive and Verify Webhooks in Node.js
Build a robust webhook receiver in Node.js, including critical signature verification to ensure incoming requests are legitimate and untampered, enhancing security.
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser'); // To parse raw request body
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
// YOUR_WEBHOOK_SECRET should be a strong, random string
// stored in your environment variables and known only to your app and the webhook sender.
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// Middleware to parse raw body for signature verification
// It's crucial to get the raw body before any other body parsing middleware.
app.use(bodyParser.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString(); // Store raw body for signature verification
}
}));
// Webhook endpoint for receiving events
app.post('/webhook', (req, res) => {
if (!WEBHOOK_SECRET) {
console.error('WEBHOOK_SECRET is not set. Cannot verify signature.');
return res.status(500).send('Webhook secret not configured.');
}
// Example for 'X-Hub-Signature-256' or similar headers (e.g., GitHub, Stripe)
const signature = req.headers['x-hub-signature-256'] || req.headers['x-signature'];
if (!signature) {
console.warn('Received webhook without signature header.');
return res.status(401).send('Signature required.');
}
// Ensure rawBody was captured
if (!req.rawBody) {
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, 'utf8');
const digest = `sha256=${hmac.digest('hex')}`;
// Compare the calculated signature with the one from the header
// Use `crypto.timingSafeEqual` for constant-time comparison to prevent timing attacks.
const isValidSignature = crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
if (!isValidSignature) {
console.warn('Invalid webhook signature received.');
return res.status(403).send('Invalid signature.');
}
console.log('Webhook received and signature verified successfully!');
console.log('Event Data:', req.body);
// Process the webhook event payload here
// e.g., save to database, trigger another service, etc.
res.status(200).send('Webhook received and processed.');
});
app.listen(PORT, () => {
console.log(`Webhook receiver listening on http://localhost:${PORT}`);
});
How it works: This Node.js Express snippet demonstrates how to build a secure webhook receiver. The most critical aspect of webhook security is signature verification. The code uses `crypto` to compute an HMAC SHA256 signature from the raw request body and a shared secret, then compares it with the signature provided in the webhook header (e.g., `X-Hub-Signature-256`). This ensures that the webhook request truly originated from the expected sender and that its payload has not been tampered with in transit, protecting your application from malicious or spoofed requests.