Webhooks
Um webhook entrega um POST JSON, assinado, no endereço que você cadastrou,
quando um evento acontece. Cadastre pelo console
(Configurações → Webhooks), pela API
(POST /v1/webhooks) ou pelo kadu.yaml.
Corpo
{
"event": "execution.completed",
"event_id": "5b0e…",
"delivered_at": "2026-10-31T12:04:51.210Z",
"organization_id": "1d7a…",
"workspace_id": "9c3f…",
"data": { "…": "o recurso do evento, em snake_case" }
}
| Campo | O que é |
|---|---|
event | o nome do evento (lista em Webhooks) |
event_id | id do evento: o mesmo em todas as tentativas de entrega dele |
delivered_at | quando esta entrega saiu |
organization_id | a organização |
workspace_id | o workspace do evento, ou null num evento da organização |
data | o recurso do evento. custom_metadata e metadata vão como foram gravados. |
Um webhook com workspace recebe só os eventos daquele workspace. Um webhook sem workspace recebe todos.
Headers
| Header | Conteúdo |
|---|---|
X-Kobana-App | kadu |
X-Kobana-Environment | production ou staging. Confira antes de processar: uma entrega de staging nunca deve tocar dados de produção. |
X-Kobana-Webhook-Id | o webhook que gerou a entrega |
X-Kobana-Event | o nome do evento |
X-Kobana-Event-Id | o id do evento |
X-Kobana-Delivery-Id | o id desta entrega |
X-Kobana-Timestamp | segundos Unix em que a entrega foi assinada |
X-Kobana-Signature | a assinatura |
webhook-id, webhook-timestamp, webhook-signature | os mesmos três valores, no layout Standard Webhooks |
Authorization ou o header configurado | a credencial que você configurou no webhook (Bearer, Basic ou header) |
Conferir a assinatura
A assinatura segue o Standard Webhooks: qualquer biblioteca de verificação compatível serve.
- Signing secret:
whsec_seguido de 32 bytes em base64. A chave do HMAC são os bytes decodificados depois do prefixo. - Conteúdo assinado:
<delivery_id>.<timestamp>.<corpo cru>, com oX-Kobana-Delivery-Id(=webhook-id) e oX-Kobana-Timestamp. - Header:
v1,<base64(HMAC-SHA256)>. Durante uma rotação, o header traz uma assinatura por secret ainda válido, separadas por espaço. Aceite a entrega se qualquer uma casar. - Janela: recuse uma entrega com timestamp a mais de 5 minutos do seu relógio, para limitar replay.
import base64, hashlib, hmac, time
def verificar(secret: str, delivery_id: str, timestamp: str, corpo: bytes, assinatura: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
chave = base64.b64decode(secret.removeprefix("whsec_"))
conteudo = f"{delivery_id}.{timestamp}.".encode() + corpo
esperado = base64.b64encode(hmac.new(chave, conteudo, hashlib.sha256).digest()).decode()
return any(
hmac.compare_digest(token.split(",", 1)[1], esperado)
for token in assinatura.split()
if token.startswith("v1,")
)
Use o corpo cru, antes de qualquer parse de JSON: reserializar muda os bytes e quebra a assinatura.
:::note Header legado
X-Kobana-Signature-Sha256 é o esquema antigo do Kadu (sha256=<hex> de
timestamp.corpo) e está depreciado. Use X-Kobana-Signature.
:::
Signing secret e rotação
O signing secret aparece uma vez, na criação do webhook. Para trocar, use
POST /v1/webhooks/{id}/rotate-secret: a resposta traz o novo secret, e o
anterior continua assinando por 24 horas (previous_signing_secret_expires_at),
para você trocar a chave no receptor sem perder entregas.
Entregas e falhas
- Responda
2xxem até 10 segundos e processe depois. Qualquer outra resposta, ou o timeout, conta como falha. - O mesmo evento pode chegar mais de uma vez: use o
event_idpara descartar duplicatas. - Depois de 5 falhas seguidas, o webhook é desativado e os e-mails cadastrados nele são avisados.
GET /v1/webhook_deliverieslista as 50 entregas mais recentes, com status e resposta, ePOST /v1/webhook_deliveries/{id}/replayreenvia uma entrega.- Entregas ficam guardadas por 90 dias.
A URL de destino precisa ser pública e https. Endereços privados, de
loopback ou em http são recusados.