What Is HMAC?
HMAC stands for Hash-based Message Authentication Code. It is a cryptographic algorithm that combines a secret key with a message to produce a fixed-length signature. The receiver can recompute the same signature using the same key and message — if the signatures match, the message is authentic and has not been modified in transit.
In the context of webhooks, HMAC solves a fundamental trust problem: how do you know an HTTP request to your endpoint actually came from the service you trust, and not from a malicious third party?
How HMAC Signing Works
The signing process follows these steps:
- The sender (e.g., KnowledgeSDK) concatenates the raw request body with a timestamp.
- It computes
HMAC-SHA256(secret_key, message)to produce a hex digest. - It includes the digest in a request header:
X-KnowledgeSDK-Signature: sha256=<hex>. - Your server recomputes the same HMAC using the shared secret and compares the result.
signature = HMAC-SHA256(webhook_secret, raw_body + timestamp)
The timestamp is included to prevent replay attacks — an attacker capturing a valid request and re-sending it later.
Verifying a KnowledgeSDK Webhook in Node.js
import crypto from "crypto";
export function verifyWebhookSignature(
rawBody: string,
signature: string,
secret: string,
timestamp: string
): boolean {
const message = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac("sha256", secret)
.update(message)
.digest("hex");
// Use timingSafeEqual to prevent timing attacks
const sigBuffer = Buffer.from(signature.replace("sha256=", ""), "hex");
const expectedBuffer = Buffer.from(expected, "hex");
if (sigBuffer.length !== expectedBuffer.length) return false;
return crypto.timingSafeEqual(sigBuffer, expectedBuffer);
}
Key points in this implementation:
- Use
crypto.timingSafeEqualinstead of===. String comparison short-circuits on the first differing character, leaking information about how close an attacker's guess is. - Reject stale timestamps. Refuse requests where the timestamp is more than 5 minutes old.
- Read the raw body. Parsing JSON before hashing will produce a different byte sequence. Always hash the raw request body string.
Why Not Just Check the API Key?
Your API key (knowledgesdk_live_*) authenticates outbound requests you make to KnowledgeSDK. Webhooks flow in the opposite direction — KnowledgeSDK calls you. Your endpoint is public, so anyone could POST to it. HMAC signing is the mechanism that proves inbound webhook requests are genuine.
Common Mistakes
- Parsing the body with a JSON middleware before hashing it (alters whitespace).
- Comparing signatures with
===instead of a constant-time function. - Forgetting to validate the timestamp, leaving the endpoint vulnerable to replay attacks.
- Storing the webhook secret in source control instead of environment variables.
Further Reading
- RFC 2104 — HMAC specification
- Related: Webhook, API Key