Aller au contenu

Verify webhook signatures

Ce contenu n’est pas encore disponible dans votre langue.

When a secret is configured, Subnoto sends an HMAC signature in the request. Verify it to ensure the request came from Subnoto and was not tampered with.

HeaderDescription
X-Webhook-IdWebhook UUID that sent the request
X-Webhook-SignaturePresent when a secret is set. Format: t=<timestamp>,v1=<hmac-sha256-hex>

The signature is computed over the string t:<timestamp>:<raw-body>, where <raw-body> is the exact request body (e.g. raw JSON or form string). Use the same encoding as received (e.g. UTF-8).

verify.js
import crypto from "crypto";
function verifyWebhookSignature(signatureHeader, rawBody, secret) {
const match = signatureHeader?.match(/t=(\d+),v1=(.+)/);
if (!match) return false;
const [, timestamp, sigValue] = match;
// Optional: reject old requests (e.g. 5 minutes)
const now = Date.now();
if (Math.abs(now - Number(timestamp)) > 300000) return false;
const message = `t:${timestamp}:${rawBody}`;
const expected = crypto.createHmac("sha256", secret).update(message).digest("hex");
return crypto.timingSafeEqual(Buffer.from(sigValue), Buffer.from(expected));
}
verify.py
import hmac
import hashlib
import time
def verify_webhook_signature(signature_header: str, raw_body: str, secret: str) -> bool:
if not signature_header:
return False
try:
parts = signature_header.split(",")
timestamp = parts[0].split("=")[1]
sig_value = parts[1].split("=")[1]
except (IndexError, ValueError):
return False
# Optional: reject old requests (e.g. 5 minutes)
if abs(int(time.time() * 1000) - int(timestamp)) > 300_000:
return False
message = f"t:{timestamp}:{raw_body}"
expected = hmac.new(
secret.encode("utf-8"),
message.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(sig_value, expected)

For form-encoded payloads, use the raw body (e.g. payload={"eventType":...}), not the parsed JSON.