Korzen
Bot Protection
In deze documentatie

Webhooks

Elke botbeslissing, of om de paar minuten een samenvatting, naar je endpoint gestuurd en ondertekend met HMAC-SHA256: de payload, de handtekening, nieuwe pogingen en limieten.

Een webhook stuurt de beslissingen bot en bot_high van Bot Protection naar een endpoint van jou: de doorgifte naar een chatkanaal, een ticketwachtrij, je eigen fraudecontrole. Je server krijgt elke beslissing nog steeds van /v1/decide zoals voorheen; de webhook is een kopie, verzonden na het antwoord, en laat nooit een beslissing wachten.

Stel je endpoint in

Op de pagina Set-up van het dashboard, onder Webhook, schrijft een eigenaar het adres van het endpoint en kiest wat het krijgt:

  • Elke beslissing van de gekozen klassen, bot, bot_high of beide, in batches van hoogstens 100, seconden na de beslissing.
  • Een samenvatting elke 5, 15 of 60 minuten: hoeveel beslissingen bot en bot_high er waren.

Het adres is https://, op poort 443, met een naam zoals hooks.example.com, nooit een IP-adres. Korzen stuurt niets naar een adres van een privénetwerk, waar de naam ook naar verwijst, en controleert dat bij elke bezorging.

De eerste keer dat een endpoint wordt opgeslagen, toont Set-up het ondertekeningssecret, kz_whs_ en 64 tekens, één keer: kopieer het naar de configuratie van je endpoint. Maak een nieuw secret vervangt het, ook maar één keer getoond; 24 uur lang ondertekent het oude elke bezorging er nog naast, zodat je endpoint blijft controleren terwijl je de kopie bijwerkt. Stuur een test stuurt nu een ondertekende bezorging test en toont wat je endpoint antwoordde.

De payload

Elke bezorging is een POST met een JSON-body. Een batch beslissingen:

{
  "type": "decisions",
  "id": "0193a5f0-7c1e-7d2a-9b4e-3f1a2c9d8e01",
  "decisions": [
    {
      "at": "2026-10-28T12:00:00.123Z",
      "request_id": "…",
      "class": "bot_high",
      "action": "block",
      "intended_action": "block",
      "bot_score": 97,
      "mode": "block",
      "reasons": ["behaviour", "inconsistency"]
    }
  ],
  "dropped": 0
}

Elke beslissing is at, het moment van de beslissing, en deze leden van het antwoord dat /v1/decide je server gaf: request_id, class, bot_score, human_score, reasons, action, intended_action, mode, step_up, token_required, identity_limited, seen_before, crawler, decoy en own_server. Verder niets: nooit de naam van een regel, de context die je stuurde of de link van de challengepagina. dropped telt de beslissingen waarvoor deze batch geen ruimte had.

Een samenvatting:

{"type": "summary", "id": "…", "from": "2026-10-28T12:00:00Z", "to": "2026-10-28T12:15:00Z", "bot": 412, "bot_high": 37}

Een test: {"type": "test", "id": "…", "at": "…"}. Lees eerst type: er kan een nieuw type komen, en aan elk kan een veld worden toegevoegd.

id is bij elke poging van een bezorging hetzelfde: heb je hem al verwerkt, antwoord dan 2xx en laat hem vallen. Bezorgingen kunnen in een andere volgorde aankomen.

Wat een bezorging belooft

  • Minstens één keer, eenmaal in de wachtrij. Een bezorging in de wachtrij wordt geprobeerd tot je endpoint 2xx antwoordt of gepauzeerd wordt, dus dezelfde bezorging kan twee keer komen: laat een herhaling vallen op haar id.
  • Best effort daarvoor. Elke server van Korzen houdt een beslissing tot 10 seconden vast voordat hij haar in de wachtrij zet, en zet alles wat hij vasthoudt in de wachtrij als hij stopt voor een deploy. Een server die crasht, verliest wat hij vasthield, en de beslissingen boven de ruimte van een batch of de uurlimiet van je abonnement worden geteld in dropped, nooit verzonden.
  • De webhook is een kopie, niet het register. Elke beslissing blijft op de pagina Beslissingen van het dashboard, die de tools list_decisions en get_decision van de MCP-server ook lezen: zoek een beslissing daar op met haar request_id.

Controleer de handtekening

Elke bezorging draagt Korzen-Signature: t=<Unix-seconden>,v1=<hex>: de HMAC-SHA256, onder je ondertekeningssecret, van t, een punt en de body precies zoals ontvangen. Bereken hem over de ruwe bytes, vóór elke JSON-parsing, vergelijk in constante tijd en weiger een t die meer dan 5 minuten van je klok afwijkt, zodat een eenmaal opgenomen bezorging later niet opnieuw kan worden gestuurd. 24 uur na een nieuw secret volgt een tweede v1, onder het oude: accepteer een bezorging zodra een v1 klopt.

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

// raw: the body as received (a Buffer); header: Korzen-Signature
function verify(raw, header, secret) {
  const parts = header.split(",").map((p) => p.split("="));
  const t = Number(parts.find(([k]) => k === "t")?.[1]);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const mac = createHmac("sha256", secret).update(`${t}.`).update(raw).digest();
  return parts.some(([k, v]) => {
    const got = Buffer.from(k === "v1" ? v ?? "" : "", "hex");
    return got.length === mac.length && timingSafeEqual(got, mac);
  });
}
import hashlib, hmac, time

def verify(raw: bytes, header: str, secret: str) -> bool:
    parts = [p.partition("=")[::2] for p in header.split(",")]
    t = next((v for k, v in parts if k == "t"), "")
    if not t.isdigit() or abs(time.time() - int(t)) > 300:
        return False
    mac = hmac.new(secret.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
    return any(k == "v1" and hmac.compare_digest(mac, v) for k, v in parts)

Antwoorden, en nieuwe pogingen

Antwoord binnen 5 seconden met een willekeurige 2xx: de body wordt niet gelezen. Al het andere, ook een redirect, of geen antwoord op tijd, is een mislukking, en de bezorging wordt 30 seconden later opnieuw geprobeerd, daarna telkens na twee keer zo lang, 8 pogingen in ongeveer een uur. Doe het trage werk nadat je hebt geantwoord.

Zolang je endpoint faalt, wachten nieuwe beslissingen, tot 100, en de rest wordt geteld in dropped. Een endpoint dat een uur lang faalt, wordt gepauzeerd: er wordt niets naartoe gestuurd, de beslissingen in de tussentijd worden niet verzonden, en elke eigenaar krijgt een e-mail. Set-up toont de status of de fout van de laatste bezorging; Hervatten stuurt weer vanaf de volgende beslissing.

Limieten

Elk abonnement stuurt tot een aantal beslissingen per uur: de gratis proefperiode 1.000, Starter 10.000, Pro 50.000, Scale 200.000. Daarboven worden de beslissingen geteld in de dropped van de volgende batch, en het volgende uur begint opnieuw. Een samenvatting wordt altijd verzonden. Een webhook telt niet mee voor je maandelijkse beslissingen.