Skip to main content

Webhook Signature Validation

Once you generate the Webhook Secret Key from the Settings page of the AML Watcher App, every HTTP webhook response will include an X-Signature key in the headers.

Steps to Validate the Webhook Signature

  1. Extract the Required Information

    • Get the raw response payload from the webhook response.
    • Retrieve the signature value from the X-Signature header.
  2. Re-serialize the Payload

    • Parse the raw JSON payload into an object.
    • Re-serialize the object into a JSON string with the following considerations:
      • Sort Keys: Ensure the keys in the JSON object are sorted alphabetically.
      • Compact Format: Remove unnecessary spaces between commas and colons.
  3. Generate a Computed Signature

    • Use your Webhook Secret Key along with the raw response payload.
    • Compute the HMAC-SHA256 hash of the re-serialized payload using your secret key.
  4. Compare the Signatures

    • Compare your Computed Signature with the signature value provided in the X-Signature header.
    • Use a safe comparison method to prevent security risks.
  5. Verify the Result

    • If the two signatures match, the webhook response is authentic and can be trusted.
    • If the signatures do not match, reject the response and take appropriate security measures.

Important Notes

  • Always use a secure hash function (HMAC-SHA256).
  • Ensure the raw response payload is unaltered before computing the signature.
const crypto = require("crypto");

// Signature verification function
function verifySignature(req) {
const webhookSecretKey = "YOUR_SECRET_KEY";
const receivedSignature = req.headers["x-signature"];

if (!receivedSignature) {
return false;
}

// Sort the object
const sortedObj = sortObjectKeys(req.body);

// Re-serialize the request body in a consistent format
const reSerializedData = JSON.stringify(sortedObj);

// Compute the HMAC-SHA256 signature using the secret key and re-serialized payload
const computedSignature = crypto
.createHmac("sha256", webhookSecretKey)
.update(reSerializedData)
.digest("hex");

// Securely compare the signatures using timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(receivedSignature, "utf8"),
Buffer.from(computedSignature, "utf8")
);
}

// Function to recursively sort object keys
function sortObjectKeys(obj) {
if (Array.isArray(obj)) {
return obj.map((item) =>
typeof item === "object" ? sortObjectKeys(item) : item
);
} else if (obj !== null && typeof obj === "object") {
return Object.keys(obj)
.sort()
.reduce((acc, key) => {
acc[key] = sortObjectKeys(obj[key]); // Recursively sort nested objects
return acc;
}, {});
}
return obj;
}