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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs