KnowFirm signs every webhook delivery with HMAC-SHA256 so you can verify the payload was sent by KnowFirm and hasn't been tampered with. This guide shows how to verify signatures in Node.js, Python, and Go.
How webhook signing works
When KnowFirm delivers a webhook, it includes two headers:
X-KnowFirm-Signature— the HMAC-SHA256 hex digest of the raw request body, keyed with your webhook secret.X-KnowFirm-Timestamp— the Unix timestamp (seconds) when the payload was signed. Use this to reject replayed deliveries.
Your webhook secret is shown once when you configure the webhook in Dashboard → Monitors. Store it securely — it never appears again (you can rotate it by deleting and re-creating the webhook).
Verification steps
- Read the raw request body (before JSON parsing).
- Compute
HMAC-SHA256(raw_body, webhook_secret)and hex-encode it. - Compare your computed signature to
X-KnowFirm-Signatureusing a constant-time comparison. - Check that
X-KnowFirm-Timestampis within an acceptable window (e.g. 5 minutes) to guard against replay attacks. - If either check fails, respond with
400and discard the payload.
Node.js / Express
import crypto from "node:crypto";
const WEBHOOK_SECRET = process.env.KNOWFIRM_WEBHOOK_SECRET;
const MAX_AGE_SECONDS = 300; // 5 minutes
function verifyWebhook(req) {
const signature = req.headers["x-knowfirm-signature"];
const timestamp = req.headers["x-knowfirm-timestamp"];
if (!signature || !timestamp) return false;
// Replay guard
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (age > MAX_AGE_SECONDS) return false;
// Constant-time signature comparison
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(req.rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}
Python / Flask
import hmac, hashlib, time, os
WEBHOOK_SECRET = os.environ["KNOWFIRM_WEBHOOK_SECRET"].encode()
MAX_AGE_SECONDS = 300
def verify_webhook(request):
signature = request.headers.get("X-KnowFirm-Signature", "")
timestamp = request.headers.get("X-KnowFirm-Timestamp", "")
if not signature or not timestamp:
return False
# Replay guard
if abs(time.time() - float(timestamp)) > MAX_AGE_SECONDS:
return False
# Constant-time signature comparison
expected = hmac.new(WEBHOOK_SECRET, request.data, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature, expected)
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"math"
"net/http"
"os"
"strconv"
"time"
)
var webhookSecret = []byte(os.Getenv("KNOWFIRM_WEBHOOK_SECRET"))
const maxAgeSeconds = 300
func verifyWebhook(r *http.Request) bool {
sig := r.Header.Get("X-KnowFirm-Signature")
ts := r.Header.Get("X-KnowFirm-Timestamp")
if sig == "" || ts == "" {
return false
}
// Replay guard
t, err := strconv.ParseFloat(ts, 64)
if err != nil || math.Abs(float64(time.Now().Unix())-t) > maxAgeSeconds {
return false
}
body, _ := io.ReadAll(r.Body)
mac := hmac.New(sha256.New, webhookSecret)
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(sig), []byte(expected))
}
Testing your endpoint
Before going live, use the Send test ping button in Dashboard → Monitors. KnowFirm sends a signed {"type":"webhook.ping"} payload to your endpoint. Verify it returns 200 — if it returns 400, check that your secret matches and you're reading the raw body (not the parsed JSON).
KnowFirm retries failed deliveries up to 3 times with exponential backoff. Delivery history (status codes and timestamps) is visible at Dashboard → Webhooks.
FAQ
What algorithm does KnowFirm use for webhook signatures? HMAC-SHA256. The signature is a hex-encoded digest of the raw request body, keyed with your webhook secret.
How do I rotate my webhook secret? Delete the webhook in Dashboard → Monitors and re-create it. The new secret is shown once. Update your server's environment variable before the next delivery.
What happens if my endpoint is down? KnowFirm retries failed deliveries (non-2xx responses or timeouts) up to 3 times. After 3 failures the delivery is marked as failed in your delivery history. No redirects are followed.
Can I filter which events I receive?
Yes. Configure an event allow-list on the webhook to subscribe to specific event types (e.g. monitor.screening, quota.80). An empty allow-list means all events.