Entregas y reintentos
Ninguna entrega es "dispara y olvida". Cada evento × endpoint se convierte en un registro de entrega con estado, intentos y la captura completa de request y response — lo que transforma "el webhook no llegó" de misterio en consulta.

El ciclo de vida de una entrega
| Estado | Significado |
|---|---|
pending | Agendada, esperando procesamiento |
success | Tu endpoint respondió 2xx |
failed | Falló; reintento agendado |
exhausted | Agotó los intentos (o fallo permanente); fue a la DLQ |
Cuenta como fallo: respuesta fuera de 2xx (los redirects no se siguen, así que 3xx también falla), error de red y timeout (10 segundos por intento).
El contrato de tu endpoint
Tres reglas para un consumidor saludable:
- Responde
2xxde inmediato, sin procesar sincrónicamente. Guarda el payload (cola, tabla, log) y devuelve200al instante; procesa después. El timeout es de 10 segundos — un handler que consulta la base, llama a terceros y recién entonces responde va a superar el límite en un día de carga y disparar reintentos innecesarios. - ¿Evento desconocido? Responde
200e ignóralo. Nuevos eventos pueden empezar a emitirse sin aviso (un endpoint con la lista de eventos vacía recibe todos). Responder error a un evento que no manejas solo genera reintentos y ruido en tu cola. - Deduplica antes de procesar — mira "al menos una vez" abajo.
Reintentos: 8 intentos en ~8h30 (backoff exponencial)
Cada entrega tiene 8 intentos con backoff exponencial de base 4 minutos — la espera se duplica con cada fallo:
| Intento | Cuándo (aprox.) | Espera tras el fallo anterior |
|---|---|---|
| 1.º | inmediato | — |
| 2.º | +4min | 4min |
| 3.º | +12min | 8min |
| 4.º | +28min | 16min |
| 5.º | +1h | 32min |
| 6.º | +2h04 | 1h04 |
| 7.º | +4h12 | 2h08 |
| 8.º | +8h28 | 4h16 |
La ventana total es de ~8h30 entre el evento y el último intento (los horarios son aproximados: dependen de la cola en ese momento) — un consumidor caído por minutos u horas todavía recibe el evento sin intervención manual. La pantalla muestra el próximo intento estimado (nextRetryAt). Cada intento se vuelve a firmar con un t nuevo en la firma.
Dos situaciones se saltan el reintento y van directo a exhausted:
- Bloqueo SSRF: la URL pasó a resolver hacia una red interna — fallo permanente, reagendar no ayudaría.
- Endpoint inactivo o eliminado en el momento de la entrega.
DLQ: nada desaparece en silencio
Cuando el 8.º intento falla, la entrega pasa a exhausted y el job va a la dead-letter queue con el error registrado. El evento en sí permanece persistido e inmutable; la entrega agotada sigue listada en la pantalla, lista para el reenvío manual cuando corrijas el endpoint.
La pantalla de entregas
En la fila del endpoint, abre el historial (10 por página). Cada entrega muestra evento, estado, código HTTP, número de intentos y fecha; al expandir la fila:
- el error del último intento,
- el request body enviado,
- el response body devuelto por tu servidor,
- y el momento de la entrega exitosa (
deliveredAt).
Los headers sensibles (firma, credenciales de autenticación, Set-Cookie de la respuesta) aparecen como [REDACTED]. El listado de endpoints agrega el total de entregas y destaca en rojo la suma de fallidas + agotadas.
Reenvío manual
Las entregas failed o exhausted tienen el botón de reprocesar (también vía POST /api/v1/webhook-endpoints/{id}/deliveries/{deliveryId}/retry). El reenvío reinicia la entrega y recomienza el ciclo de intentos; si el endpoint está inactivo, la solicitud se rechaza (409 ENDPOINT_INACTIVE). Cada reenvío queda en la auditoría.
Entrega "al menos una vez"
El sistema garantiza que el evento no se pierde, no que llega exactamente una vez: un 200 que se perdió en la red genera un nuevo intento y, por lo tanto, una entrega duplicada de tu lado. Diseña el consumidor idempotente: deduplica por el header X-Webhook-Event-Id (el UUID del evento, estable entre intentos y reenvíos) antes de procesar. El header X-Webhook-Attempt trae el número del intento, útil para telemetría de tu lado.
Probar sin evento de negocio
POST /api/v1/webhook-endpoints/{id}/test dispara un evento webhook.test por el pipeline real — la entrega aparece en la pantalla como cualquier otra, con captura y reintento. Úsalo para validar firma, auth y el contrato del consumidor antes de entrar en producción.