WebhookDelivery
Um WebhookDelivery é uma tentativa de POST de payload de webhook para sua URL alvo. O dispatcher cria uma linha por tentativa; retries produzem linhas adicionais ligadas ao mesmo system_event_id.
Propósito
- Prover um histórico auditável de toda tentativa (status, payload, response, duração).
- Permitir replay de entregas falhas sem reemitir o evento subjacente.
- Alimentar a UI de webhook deliveries em
/manage.
Schema
| Campo | Tipo | Descrição |
|---|---|---|
id | UUID | ID interno. |
webhook_id | UUID | Webhook dono. |
system_event_id | UUID | O evento sendo entregue. |
attempt | int | Contador de retry 1-indexed. |
status | enum | pending, success, failed. |
request_headers | object | Headers de saída (auth values redigidos). |
request_body | object | Payload JSON enviado. |
response_status | int | null | Status HTTP recebido. |
response_headers | object | null | Headers do alvo (capped em 4 KiB). |
response_body | string | null | Body do alvo (capped em 16 KiB). |
error | string | null | Erro de transporte (timeout, DNS, TLS). |
duration_ms | int | null | Wall time da chamada HTTP. |
delivered_at | datetime | null | Quando a chamada terminou. |
next_retry_at | datetime | null | Quando o próximo retry está agendado (se houver). |
created_at / updated_at | datetime |
Status
| Status | Significado |
|---|---|
pending | Enfileirada ou em vôo; delivered_at não setado. |
success | 2xx recebido dentro do timeout. |
failed | Não-2xx, timeout ou erro de transporte. Pode retry — confira next_retry_at. |
Exemplo
{
"id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"webhook_id": "f6a7b8c9-0d1e-4f2a-3b4c-5d6e7f8a9b0c",
"system_event_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"attempt": 2,
"status": "success",
"request_headers": {
"X-Kobana-Event": "email.db.created",
"X-Kobana-Timestamp": "1747490400",
"X-Kobana-Signature": "sha256=…",
"Authorization": "Bearer [REDACTED]"
},
"request_body": { "event": "email.db.created", "data": {} },
"response_status": 200,
"response_headers": { "content-type": "application/json" },
"response_body": "{\"ok\":true}",
"error": null,
"duration_ms": 312,
"delivered_at": "2026-05-17T12:05:14.218Z",
"next_retry_at": null,
"created_at": "2026-05-17T12:05:13.906Z",
"updated_at": "2026-05-17T12:05:14.218Z"
}
Leitura
| Método | Path |
|---|---|
GET | /webhooks/{id}/deliveries?status=failed&attempt[gte]=2 |
GET | /webhooks/{id}/deliveries/{deliveryId} |
Filtros: status, attempt[gte|lt], created_at[gte|lt].
Replay
POST /webhooks/{id}/deliveries/{deliveryId}/replay enfileira uma redelivery imediata — ignora qualquer backoff pendente mas é registrado como uma nova linha (attempt não é incrementado; uma nova WebhookDelivery é appended com attempt=1 e uma referência metadata.replay_of).
Quando qualquer entrega de um evento a um webhook tem sucesso — replay incluído — as retentativas ainda agendadas para esse evento não são enviadas, e o next_retry_at das linhas failed anteriores é limpo. Um replay bem-sucedido, portanto, encerra a cadeia de backoff da entrega original.
A requisição do replay também carrega seu próprio X-Idempotency-Key, um valor novo e aleatório em vez do id do evento que toda tentativa automática usa (veja Headers). Retentativas automáticas continuam deduplicadas contra a tentativa original de propósito; um replay é um pedido deliberado de agir sobre o evento de novo, então não deve ser engolido pela mesma guarda.