Saltar al contenido principal

Webhooks

Un webhook entrega un POST JSON, firmado, en la dirección que registraste, cuando ocurre un evento. Regístralo desde la consola (Configuración → Webhooks), por la API (POST /v1/webhooks) o mediante el kadu.yaml.

Cuerpo​

{
"event": "execution.completed",
"event_id": "5b0e…",
"delivered_at": "2026-10-31T12:04:51.210Z",
"organization_id": "1d7a…",
"workspace_id": "9c3f…",
"data": { "…": "el recurso del evento, en snake_case" }
}
CampoQué es
eventel nombre del evento (lista en Webhooks)
event_idid del evento: el mismo en todos los intentos de entrega de él
delivered_atcuándo salió esta entrega
organization_idla organización
workspace_idel workspace del evento, o null en un evento de la organización
datael recurso del evento. custom_metadata y metadata van como se guardaron.

Un webhook con workspace recibe solo los eventos de ese workspace. Un webhook sin workspace recibe todos.

Headers​

HeaderContenido
X-Kobana-Appkadu
X-Kobana-Environmentproduction o staging. Verifícalo antes de procesar: una entrega de staging nunca debe tocar datos de producción.
X-Kobana-Webhook-Idel webhook que generó la entrega
X-Kobana-Eventel nombre del evento
X-Kobana-Event-Idel id del evento
X-Kobana-Delivery-Idel id de esta entrega
X-Kobana-Timestampsegundos Unix en que se firmó la entrega
X-Kobana-Signaturela firma
webhook-id, webhook-timestamp, webhook-signaturelos mismos tres valores, en el formato de Standard Webhooks
Authorization o el header configuradola credencial que configuraste en el webhook (Bearer, Basic o header)

Verificar la firma​

La firma sigue Standard Webhooks: sirve cualquier biblioteca de verificación compatible.

  • Signing secret: whsec_ seguido de 32 bytes en base64. La clave del HMAC son los bytes decodificados después del prefijo.
  • Contenido firmado: <delivery_id>.<timestamp>.<cuerpo crudo>, con el X-Kobana-Delivery-Id (= webhook-id) y el X-Kobana-Timestamp.
  • Header: v1,<base64(HMAC-SHA256)>. Durante una rotación, el header trae una firma por cada secret aún válido, separadas por espacio. Acepta la entrega si cualquiera coincide.
  • Ventana: rechaza una entrega con un timestamp a más de 5 minutos de tu reloj, para limitar el 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,")
)

Usa el cuerpo crudo, antes de cualquier parse de JSON: reserializar cambia los bytes y rompe la firma.

:::note Header heredado X-Kobana-Signature-Sha256 es el esquema antiguo de Kadu (sha256=<hex> de timestamp.cuerpo) y está obsoleto. Usa X-Kobana-Signature. :::

Signing secret y rotación​

El signing secret aparece una vez, en la creación del webhook. Para cambiarlo, usa POST /v1/webhooks/{id}/rotate-secret: la respuesta trae el nuevo secret, y el anterior sigue firmando por 24 horas (previous_signing_secret_expires_at), para que cambies la clave en el receptor sin perder entregas.

Entregas y fallas​

  • Responde 2xx en hasta 10 segundos y procesa después. Cualquier otra respuesta, o el timeout, cuenta como falla.
  • El mismo evento puede llegar más de una vez: usa el event_id para descartar duplicados.
  • Después de 5 fallas seguidas, el webhook se desactiva y se avisa a los correos registrados en él.
  • GET /v1/webhook_deliveries lista las 50 entregas más recientes, con estado y respuesta, y POST /v1/webhook_deliveries/{id}/replay reenvía una entrega.
  • Las entregas se guardan por 90 días.

La URL de destino debe ser pública y https. Las direcciones privadas, de loopback o en http se rechazan.