Verifying signatures
Every webhook delivery is signed with your webhook's secret using HMAC-SHA256. You should always verify the signature before processing the payload to confirm it came from Sotion and wasn't tampered with.
The signature is sent in the X-Webhook-Signature header as sha256={hex-digest}. To verify:
Extract the hex digest from the header (everything after
sha256=)Compute HMAC-SHA256 of the raw request body using your webhook secret as the key
Compare the computed digest with the one in the header using a constant-time comparison
Node.js
import crypto from "crypto";
function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
const actual = signature.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(actual, "hex")
);
}
// In your request handler:
const body = await request.text(); // raw body string
const signature = request.headers.get("X-Webhook-Signature");
if (!verifyWebhookSignature(body, signature, process.env.WEBHOOK_SECRET)) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(body);
// Process the event...Python
import hmac
import hashlib
def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
body,
hashlib.sha256
).hexdigest()
actual = signature.removeprefix("sha256=")
return hmac.compare_digest(expected, actual)Ruby
require "openssl"
def verify_webhook_signature(body, signature, secret)
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, body)
actual = signature.delete_prefix("sha256=")
Rack::Utils.secure_compare(expected, actual)
endWas this helpful?