Skip to content

Reports and alerts

Verify webhook signatures

Check the HMAC signature on every delivery, with Python and Node examples.

Each delivery is a POST with Content-Type: application/json and these headers:

  • X-TasScan-Signature: t=<unix seconds>,v1=<hex>[,v1=<hex>], where v1 = HMAC-SHA256(secret, "<t>.<raw body>"). Two v1 values are present while a rotated secret still overlaps; accept the delivery if any matches.
  • X-TasScan-Event (the event type), X-TasScan-Event-Id (the same for every retry and resend of one event) and X-TasScan-Delivery-Id.

Rules

  1. Always verify against the raw request body before you parse it.
  2. Reject timestamps older than 5 minutes.
  3. Keep processed X-TasScan-Event-Id values for at least 24 hours so retries and replays are ignored.
  4. Answer with any 2xx within 10 seconds. Redirects are not followed.

Python

import hashlib, hmac, time

def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
    parts = [p.split("=", 1) for p in header.split(",") if "=" in p]
    t = next((v for k, v in parts if k == "t"), None)
    sigs = [v for k, v in parts if k == "v1"]
    if t is None or not t.isdigit() or abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, s) for s in sigs)

Node

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret, header, rawBody, tolerance = 300) {
  const parts = header.split(",").map((p) => p.split("="));
  const t = parts.find(([k]) => k === "t")?.[1];
  const sigs = parts.filter(([k]) => k === "v1").map(([, v]) => v);
  if (!t || !/^\d+$/.test(t) || Math.abs(Date.now() / 1000 - Number(t)) > tolerance) return false;
  const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
  return sigs.some((s) => {
    const got = Buffer.from(s, "hex");
    return got.length === expected.length && timingSafeEqual(got, expected);
  });
}