Webhooks
Webhook Authentication
Every request Katexs sends is signed. Verify the signature before you trust a payload โ an unverified endpoint is an open door into your call data.
The signature header
Each delivery carries an x-katexs-signature header containing a timestamp and an HMAC-SHA256 hex digest of the raw request body, computed with your webhook signing secret.
text
x-katexs-signature: t=1785000461,v1=7f3a1c9e4b2d...
x-katexs-event-id: evt_01J8XQ
x-katexs-delivery-attempt: 1Get your signing secret
Go to Settings โ Integrations โ Webhooks and click Reveal signing secret. Store it as an environment variable on your server. Rotating it generates a new secret while the old one stays valid for 24 hours so you can deploy without dropping events.
Verify it
ts
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifySignature(rawBody: string, header: string | null) {
if (!header) return false;
const parts = Object.fromEntries(
header.split(",").map((p) => p.trim().split("=") as [string, string]),
);
const timestamp = Number(parts["t"]);
const provided = parts["v1"];
if (!timestamp || !provided) return false;
// Reject anything older than five minutes to block replays.
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = createHmac("sha256", process.env["KATEXS_WEBHOOK_SECRET"]!)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const a = Buffer.from(provided, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}Rules that matter
- Sign the raw body โ Verify before parsing. Re-serialized JSON produces a different digest and will never match.
- Use a timing-safe compare โ A plain === leaks timing information and is a real attack surface.
- Enforce the timestamp window โ Without it, a captured request can be replayed forever.
- Deduplicate on event id โ Retries deliver the same x-katexs-event-id. Treat it as an idempotency key.
- Fail closed โ Return 401 on a bad signature. Never process the payload anyway.
Additional layers
| Layer | When to use it |
|---|---|
| Static header secret | Your API gateway needs a key before the request reaches your app. |
| mTLS | Regulated environments where transport-level client identity is required. |
| IP allow-list | Defense in depth. Request the current egress ranges from support; they change rarely but they do change. |
| Path entropy | A hard-to-guess URL path reduces noise, but is not a substitute for signature verification. |
If you suspect a secret has leaked, rotate it immediately in Settings โ Integrations. Deliveries signed with the compromised secret stop being accepted after the 24-hour overlap.
