Korzen
Bot Protection
Nesta documentação

Webhooks

Cada decisão de bot, ou um resumo a cada poucos minutos, enviada ao seu endpoint e assinada com HMAC-SHA256: o payload, a assinatura, as novas tentativas e os limites.

Um webhook envia as decisões bot e bot_high do Bot Protection a um endpoint seu: o repasse de um canal de chat, uma fila de tickets, a sua própria análise de fraude. Seu servidor continua recebendo cada decisão de /v1/decide como antes; o webhook é uma cópia, enviada depois da resposta, e nunca faz uma decisão esperar.

Defina seu endpoint

Na página Configuração do painel, em Webhook, um proprietário escreve o endereço do endpoint e escolhe o que ele recebe:

  • Cada decisão das classes escolhidas, bot, bot_high ou ambas, em lotes de até 100, segundos depois da decisão.
  • Um resumo a cada 5, 15 ou 60 minutos: quantas decisões bot e bot_high houve.

O endereço é https://, na porta 443, com um nome como hooks.example.com, nunca um endereço IP. A Korzen não envia nada a um endereço de rede privada, seja qual for o endereço do nome, e verifica isso a cada entrega.

Na primeira vez que um endpoint é salvo, a Configuração mostra o segredo de assinatura dele, kz_whs_ e 64 caracteres, uma única vez: copie-o para a configuração do seu endpoint. Criar um novo segredo o substitui, também mostrado uma única vez; por 24 horas o antigo ainda assina cada entrega junto com ele, para que seu endpoint continue verificando enquanto você atualiza a cópia dele. Enviar um teste envia agora uma entrega test assinada e mostra o que seu endpoint respondeu.

O payload

Cada entrega é um POST com corpo JSON. Um lote de decisões:

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

Cada decisão é at, o momento em que foi tomada, e estes membros da resposta que /v1/decide deu ao seu servidor: 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. Nada além disso: nunca o nome de uma regra, o context que você enviou ou o link da página de desafio. dropped conta as decisões para as quais este lote não teve espaço.

Um resumo:

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

Um teste: {"type": "test", "id": "…", "at": "…"}. Leia type primeiro: um novo tipo pode surgir, e um campo pode ser acrescentado a qualquer um deles.

id é o mesmo em cada tentativa de uma entrega: se você já a tratou, responda 2xx e descarte-a. As entregas podem chegar fora de ordem.

O que uma entrega promete

  • Pelo menos uma vez, depois de enfileirada. Uma entrega enfileirada é tentada até seu endpoint responder 2xx ou ser pausado, então a mesma entrega pode chegar duas vezes: descarte a repetição pelo id.
  • Melhor esforço antes disso. Cada servidor da Korzen guarda uma decisão por até 10 segundos antes de enfileirá-la, e enfileira tudo o que guarda quando para para um deploy. Um servidor que cai perde o que guardava, e as decisões além do espaço de um lote ou do limite por hora do seu plano são contadas em dropped, nunca enviadas.
  • O webhook é uma cópia, não o registro. Cada decisão fica na página Decisões do painel, que as ferramentas list_decisions e get_decision do servidor MCP também leem: procure uma decisão lá pelo request_id.

Confira a assinatura

Cada entrega traz Korzen-Signature: t=<segundos Unix>,v1=<hex>: o HMAC-SHA256, sob o seu segredo de assinatura, de t, um ponto e o corpo exatamente como recebido. Calcule-o sobre os bytes brutos, antes de qualquer parsing de JSON, compare em tempo constante e recuse um t a mais de 5 minutos do seu relógio, para que uma entrega gravada uma vez não possa ser reenviada depois. Por 24 horas depois de um novo segredo, um segundo v1 vem em seguida, sob o antigo: aceite uma entrega quando qualquer v1 confere.

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)

Resposta e novas tentativas

Responda qualquer 2xx em até 5 segundos: o corpo não é lido. Qualquer outra coisa, um redirecionamento também, ou nenhuma resposta a tempo, é uma falha, e a entrega é tentada de novo 30 segundos depois, e depois com o dobro do tempo a cada vez, 8 tentativas em cerca de uma hora. Faça o trabalho lento depois de responder.

Enquanto seu endpoint falha, as novas decisões esperam, até 100, e as demais são contadas em dropped. Um endpoint que falha por uma hora é pausado: nada é enviado a ele, as decisões nesse meio-tempo não são enviadas, e cada proprietário recebe um email. A Configuração mostra o status ou o erro da última entrega; Retomar volta a enviar a partir da próxima decisão.

Limites

Cada plano envia até um número de decisões por hora: o teste gratuito 1.000, Starter 10.000, Pro 50.000, Scale 200.000. Passado esse número, as decisões são contadas no dropped do próximo lote, e a hora seguinte começa do zero. Um resumo é sempre enviado. Um webhook não conta para as suas decisões do mês.