Korzen
Bot Protection
In questa documentazione

Webhook

Ogni decisione bot, o un riepilogo ogni pochi minuti, inviata al tuo endpoint e firmata con HMAC-SHA256: il payload, la firma, i nuovi tentativi e i limiti.

Un webhook invia le decisioni bot e bot_high di Bot Protection a un tuo endpoint: l'inoltro a un canale di chat, una coda di ticket, la tua revisione antifrode. Il tuo server continua a ricevere ogni decisione da /v1/decide come prima; il webhook ne è una copia, inviata dopo la risposta, e non fa mai aspettare una decisione.

Imposta il tuo endpoint

Nella pagina Configurazione della dashboard, sotto Webhook, un proprietario scrive l'indirizzo dell'endpoint e sceglie cosa riceve:

  • Ogni decisione delle classi scelte, bot, bot_high o entrambe, in lotti fino a 100, pochi secondi dopo la decisione.
  • Un riepilogo ogni 5, 15 o 60 minuti: quante decisioni bot e bot_high ci sono state.

L'indirizzo è https://, sulla porta 443, con un nome come hooks.example.com, mai un indirizzo IP. Korzen non invia nulla a un indirizzo di una rete privata, qualunque sia l'indirizzo del nome, e lo verifica a ogni consegna.

La prima volta che un endpoint viene salvato, Configurazione mostra il suo segreto di firma, kz_whs_ e 64 caratteri, una sola volta: copialo nella configurazione del tuo endpoint. Crea un nuovo segreto lo sostituisce, mostrato anch'esso una sola volta; per 24 ore il vecchio firma ancora ogni consegna insieme al nuovo, così il tuo endpoint continua a verificare mentre aggiorni la sua copia. Invia un test invia ora una consegna test firmata e mostra cosa ha risposto il tuo endpoint.

Il payload

Ogni consegna è un POST con un corpo JSON. Un lotto di decisioni:

{
  "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
}

Ogni decisione è at, il momento in cui è stata presa, e questi membri della risposta che /v1/decide ha dato al tuo server: request_id, class, bot_score, human_score, reasons, action, intended_action, mode, step_up, token_required, identity_limited, seen_before, crawler, decoy e own_server. Nient'altro: mai il nome di una regola, il context che hai inviato o il link della pagina di challenge. dropped conta le decisioni per cui questo lotto non aveva spazio.

Un riepilogo:

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

Un test: {"type": "test", "id": "…", "at": "…"}. Leggi prima type: può arrivare un tipo nuovo, e a ognuno può essere aggiunto un campo.

id è lo stesso a ogni tentativo di una consegna: se l'hai già gestita, rispondi 2xx e scartala. Le consegne possono arrivare fuori ordine.

Cosa promette una consegna

  • Almeno una volta, una volta in coda. Una consegna in coda viene tentata finché il tuo endpoint risponde 2xx o viene messo in pausa, quindi la stessa consegna può arrivare due volte: scarta la ripetizione in base al suo id.
  • Best effort prima di allora. Ogni server di Korzen tiene una decisione fino a 10 secondi prima di metterla in coda, e mette in coda tutto ciò che tiene quando si ferma per un deploy. Un server che va in crash perde ciò che teneva, e le decisioni oltre lo spazio di un lotto o il limite orario del tuo piano sono contate in dropped, mai inviate.
  • Il webhook è una copia, non il registro. Ogni decisione resta nella pagina Decisioni della dashboard, che anche gli strumenti list_decisions e get_decision del server MCP leggono: cerca lì una decisione con il suo request_id.

Verifica la firma

Ogni consegna porta Korzen-Signature: t=<secondi Unix>,v1=<hex>: l'HMAC-SHA256, con il tuo segreto di firma, di t, un punto e il corpo esattamente come ricevuto. Calcolalo sui byte grezzi, prima di qualsiasi parsing JSON, confronta in tempo costante e rifiuta un t a più di 5 minuti dal tuo orologio, così una consegna registrata una volta non può essere rinviata più tardi. Per 24 ore dopo un nuovo segreto segue un secondo v1, con il vecchio: accetta una consegna quando un qualsiasi v1 corrisponde.

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)

Risposta e nuovi tentativi

Rispondi con un qualsiasi 2xx entro 5 secondi: il corpo non viene letto. Qualsiasi altra cosa, anche un redirect, o nessuna risposta in tempo, è un errore, e la consegna viene ritentata 30 secondi dopo, poi ogni volta dopo il doppio del tempo, 8 tentativi in circa un'ora. Fai il lavoro lento dopo aver risposto.

Finché il tuo endpoint fallisce, le nuove decisioni aspettano, fino a 100, e le altre sono contate in dropped. Un endpoint che fallisce per un'ora viene messo in pausa: non gli viene inviato nulla, le decisioni nel frattempo non vengono inviate, e ogni proprietario riceve un'email. Configurazione mostra lo stato o l'errore dell'ultima consegna; Riprendi torna a inviare dalla prossima decisione.

Limiti

Ogni piano invia fino a un certo numero di decisioni all'ora: la prova gratuita 1.000, Starter 10.000, Pro 50.000, Scale 200.000. Oltre, le decisioni sono contate nel dropped del lotto successivo, e l'ora seguente riparte da zero. Un riepilogo viene sempre inviato. Un webhook non conta nelle tue decisioni del mese.