Saltar al contenido principal

WebhookDelivery

Un WebhookDelivery es un intento de hacer POST de un payload de webhook a su URL de destino. El dispatcher crea una fila por intento; los reintentos producen filas adicionales vinculadas al mismo system_event_id.

Propósito​

  • Proveer un historial auditable de cada intento de webhook (status, payload, response, duración).
  • Permitir la repetición (replay) de entregas fallidas sin volver a emitir el evento subyacente.
  • Alimentar la UI de webhook deliveries en /manage.

Schema​

CampoTipoDescripción
idUUIDID interno.
webhook_idUUIDWebhook propietario.
system_event_idUUIDEl evento que se está entregando.
attemptintContador de reintentos, indexado desde 1.
statusenumpending, success, failed.
request_headersobjectHeaders salientes (valores de auth redactados).
request_bodyobjectEl payload JSON enviado.
response_statusint | nullStatus HTTP recibido.
response_headersobject | nullHeaders del destino (limitado a 4 KiB).
response_bodystring | nullBody del destino (limitado a 16 KiB).
errorstring | nullError de transporte (timeout, fallo de DNS, TLS).
duration_msint | nullTiempo real de la llamada HTTP.
delivered_atdatetime | nullCuándo terminó la llamada.
next_retry_atdatetime | nullCuándo está programado el próximo reintento (si lo hay).
created_at / updated_atdatetime

Estado​

EstadoSignificado
pendingEn cola o en curso; delivered_at no está establecido.
successSe recibió 2xx dentro del timeout.
failedNo-2xx, timeout o error de transporte. Puede reintentar — revisa next_retry_at.

Ejemplo​

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

Lectura​

MétodoPath
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 encola una redelivery inmediata — ignora cualquier backoff pendiente, pero se registra como una nueva fila (attempt no se incrementa; se agrega un nuevo WebhookDelivery con attempt=1 y una referencia metadata.replay_of).

Cuando cualquier entrega de un evento a un webhook tiene éxito — replay incluido — los reintentos aún programados para ese evento no se envían, y el next_retry_at de sus filas failed anteriores se limpia. Un replay exitoso, por lo tanto, termina la cadena de backoff de la entrega original.

La solicitud del replay también lleva su propio X-Idempotency-Key, un valor nuevo y aleatorio en lugar del id del evento que usa cada intento automático (ver Headers). Los reintentos automáticos se siguen deduplicando contra el intento original a propósito; un replay es una solicitud deliberada de actuar sobre el evento de nuevo, así que no debe ser absorbido por la misma guarda.