JAVASCRIPT
Receiving and Verifying Webhook Payloads Securely
Learn how to set up a Node.js Express endpoint to securely receive and verify webhook payloads from external services using digital signature headers for data integrity and authenticity.
const express = require('express');
const crypto = require('crypto');
const app = express();
const port = 3001;
// Load environment variables
// require('dotenv').config();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'your_super_secret_key'; // Use a strong, unique secret
// Middleware to parse raw body for signature verification
// It's crucial to get the raw body as a string or buffer before any JSON parsing
app.use(express.json({ verify: (req, res, buf) => {
req.rawBody = buf; // Attach raw body to request object
}}));
app.post('/webhook/stripe', (req, res) => {
const signature = req.headers['stripe-signature']; // Example: Stripe uses 'stripe-signature'
const payload = req.rawBody.toString(); // Get the raw payload for verification
if (!signature) {
return res.status(400).send('Webhook signature header missing.');
}
// For Stripe, the signature typically includes a timestamp and multiple signatures.
// You'd usually parse this to get the relevant signature for your secret.
// This example simplifies for demonstration, assuming a simple HMAC-SHA256 signature.
// In a real Stripe integration, use `stripe.webhooks.constructEvent`.
try {
// A generic verification process using HMAC-SHA256
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(payload);
const expectedSignature = `sha256=${hmac.digest('hex')}`;
// Compare the expected signature with the one from the header
// For robust comparison, use `crypto.timingSafeEqual` to prevent timing attacks
// Note: This simple comparison might not match complex headers like Stripe's.
// Adjust `signature.split(',').find(...)` logic for specific providers.
if (!crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(signature))) {
console.warn('Webhook signature mismatch!');
return res.status(401).send('Webhook signature mismatch.');
}
console.log('Webhook signature verified successfully!');
const event = req.body; // Parse the JSON body after verification
// Process the webhook event
switch (event.type) {
case 'payment_intent.succeeded':
console.log('Payment successful event:', event.data.object.amount);
// Handle successful payment logic
break;
case 'customer.created':
console.log('New customer created:', event.data.object.email);
// Handle new customer logic
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).send('Webhook received and processed.');
} catch (err) {
console.error('Webhook processing error:', err.message);
res.status(400).send(`Webhook Error: ${err.message}`);
}
});
app.listen(port, () => {
console.log(`Webhook listener app listening at http://localhost:${port}`);
});
How it works: This Node.js Express snippet demonstrates how to set up an endpoint to receive and securely verify webhook payloads. Webhooks are a critical API integration pattern where external services notify your application of events. Verifying the signature, typically provided in a request header and generated using a shared secret, is essential to ensure that the payload genuinely originated from the trusted service and hasn't been tampered with. The code uses the `crypto` module to compute an HMAC-SHA256 signature of the raw request body and compares it against the received signature, preventing unauthorized or forged webhook calls.