In this lesson, you will learn how to verify the authenticity of incoming webhook requests using HMAC signatures to ensure your server only processes legitimate data from trusted providers.
The Webhook Vulnerability
When you expose a public endpoint to receive webhooks (like those from Stripe, GitHub, or Shopify), any actor on the internet can send an HTTP POST request to that URL. If you blindly parse the JSON body and act upon it, you are vulnerable to "webhook spoofing," where an attacker triggers unauthorized events in your system by imitating a legitimate provider.
Protecting with HMAC Signatures
To prove that a request genuinely originated from your provider, most services include an X-Hub-Signature (or similar) header in the request. This header contains a Hash-based Message Authentication Code (HMAC).
The provider takes the raw request body and signs it using a "shared secret" known only to them and your application. To validate this, your Node.js server must:
- Access the raw request body (before it is parsed into a JSON object).
- Use the shared secret and the chosen hashing algorithm (usually SHA-256) to re-calculate the HMAC of the raw body.
- Compare your calculated signature with the one provided in the header using a constant-time comparison function to prevent timing attacks.
Practical Implementation
Using the standard crypto module, your validation logic should look like this:
const crypto = require('crypto');
function isValidSignature(rawBody, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
// Use timingSafeEqual to prevent side-channel timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
By ensuring the signature matches, you cryptographically guarantee that the payload has not been tampered with and originated from a sender who possesses your shared secret. If the signatures do not match, you must reject the request immediately with a 401 Unauthorized status and log the failed attempt.
Key Takeaway: Never process webhook data without first verifying its integrity using an HMAC signature generated from the raw request body and a securely stored shared secret.