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:

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

  1. Read the raw request body (before JSON parsing).
  2. Compute HMAC-SHA256(raw_body, webhook_secret) and hex-encode it.
  3. Compare your computed signature to X-KnowFirm-Signature using a constant-time comparison.
  4. Check that X-KnowFirm-Timestamp is within an acceptable window (e.g. 5 minutes) to guard against replay attacks.
  5. If either check fails, respond with 400 and 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.