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.
Headers
Section titled “Headers”| Header | Description |
|---|---|
| X-Webhook-Id | Webhook UUID that sent the request |
| X-Webhook-Signature | Present 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 in Node.js
Section titled “Verify in Node.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 in Python
Section titled “Verify in Python”import hmacimport hashlibimport 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.
Troubleshooting
Section titled “Troubleshooting”- Confirm the webhook is active
- Ensure the URL is publicly reachable and accepts POST
- Check SSL if SSL verification is on
- Inspect server logs for the request
- Use the exact secret from Subnoto
- Use the raw request body (no parsing/trimming)
- For form-encoded: use the form string, not the JSON inside
- Ensure timestamp is within your tolerance window
- Check the webhook is subscribed to that event type
- Check workspace filter includes the envelope’s workspace
- Confirm the webhook is active