# Entrega y fiabilidad de webhooks: reintentos, orden, dead-letter

> Un webhook es un callback HTTP de servidor a servidor: la plataforma de mensajería hace POST de un evento a la URL del consumidor. La entrega fiable es al menos una vez, por lo que los productores reintentan las entregas fallidas con backoff exponencial y jitter, envían a dead-letter tras un tope, y los consumidores deben ser idempotentes porque se esperan duplicados y eventos fuera de orden.

- URL canónica: https://www.broadnet.me/es/reference/webhook-delivery-and-reliability
- Idioma: es
- Última actualización: 2026-07-19
- Editor: BroadNet (https://www.broadnet.me)

La arquitectura estándar de webhook/callback y su modelo de fiabilidad: entrega al menos una vez, backoff exponencial con jitter en los reintentos, timeouts de entrega, garantías de orden, colas dead-letter, consumidores idempotentes y salud del endpoint / circuit-breaking para proteger a un receptor que falla.

## Arquitectura de un webhook

Un webhook invierte los papeles habituales de cliente y servidor: en lugar de que la aplicación sondee la plataforma de mensajería en busca de novedades, la plataforma hace POST de un evento a una URL que la aplicación registró: un cambio de estado de entrega, un mensaje entrante, un evento de cuenta. Esta es la mitad push del modelo asíncrono cuya mitad pull es sondear GET /messages/{id}. El endpoint del consumidor es un servidor HTTPS ordinario que debe aceptar un POST, hacer lo mínimo necesario para registrar el evento de forma duradera y devolver un 2xx con rapidez. Un patrón común e importante es acusar recibo al recibir y procesar de forma asíncrona: el endpoint escribe el evento en bruto en una cola o un log, devuelve 200, y un worker separado hace el trabajo real, de modo que una lógica de negocio lenta nunca provoque un timeout y un reintento del productor.

*Un POST de webhook de estado de entrega al consumidor*

```http
POST /webhooks/dlr HTTP/1.1
Host: app.example
Content-Type: application/json
Webhook-Id: 8b1a...
Webhook-Timestamp: 1752916522
Webhook-Signature: v1,3f2b...

{
  "event": "message.delivered",
  "message_id": "01HZY8Q3M6",
  "status": "delivered",
  "occurred_at": "2026-07-19T09:15:25Z"
}
```

## Garantías de entrega: al menos una vez

La entrega distribuida no puede ser simultáneamente exactamente una vez y estar disponible, por lo que casi todo sistema de webhooks en producción elige al menos una vez: el productor sigue reintentando hasta ver un acuse de éxito, aceptando que un consumidor pueda recibir ocasionalmente el mismo evento más de una vez. Los extremos alternativos son peores: como mucho una vez descarta eventos ante el primer fallo, y exactamente una vez real exige una coordinación distribuida que el transporte HTTP no proporciona. Al menos una vez impone una obligación al consumidor: debe ser idempotente, deduplicando sobre un identificador de evento estable para que un evento reentregado sea inocuo. El contrato de acuse es sencillo: cualquier 2xx significa «deja de reintentar»; cualquier otra cosa (o un timeout) significa «reintenta más tarde».

## Reintentos con backoff exponencial y jitter

Cuando una entrega falla o expira, el productor reintenta según una planificación que crece exponencialmente —un retardo base duplicado en cada intento hasta un techo— para no machacar a un endpoint brevemente indisponible. Fundamentalmente, el retardo incluye un jitter aleatorio: sin él, una flota de eventos que fallaron todos durante la misma caída reintentaría al unísono y produciría una estampida sincronizada en el instante en que el endpoint se recupera. El jitter completo (un valor aleatorio entre cero y la cota de backoff actual) reparte esa carga con suavidad. Los reintentos continúan hasta que el evento tiene éxito o se alcanza un tope total de intentos o de tiempo, tras lo cual el evento pasa a dead-letter en vez de reintentarse eternamente.

*Backoff exponencial con jitter completo*

```text
base = 2s ; factor = 2 ; cap = 3600s
attempt n bound = min(cap, base * factor^(n-1))
delay(n)        = random(0, bound)      # full jitter

n=1  bound  2s    delay in [0, 2s]
n=2  bound  4s    delay in [0, 4s]
n=3  bound  8s    delay in [0, 8s]
n=4  bound 16s    delay in [0, 16s]
...  until success or attempt/time cap -> dead-letter
```

## Timeouts y orden

Un productor fija un timeout corto por entrega —comúnmente unos pocos segundos— y trata un timeout igual que un fallo: se abandona la conexión y se planifica el reintento del evento. Esta es la razón mecánica por la que los consumidores deberían acusar rápido y procesar de forma asíncrona. El orden es la garantía más difícil. Como los reintentos y la entrega en paralelo se entrelazan, un flujo de webhooks ingenuo solo está ordenado en el mejor de los casos: un evento anterior reintentado puede llegar después de uno posterior. Los sistemas que necesitan orden o bien entregan por clave en secuencia (todos los eventos de un id de mensaje en una única partición ordenada, bloqueando ante un fallo) o, más comúnmente, hacen que el payload sea auto-ordenado: cada evento porta una marca de tiempo y una secuencia monótona para que el consumidor pueda descartar una actualización obsoleta en vez de depender del orden de llegada.

*Estrategias de orden para flujos de webhooks*

| Estrategia | Cómo | Costo |
|---|---|---|
| Best-effort | Entregar según disponibilidad, reintentar de forma independiente | Los eventos pueden llegar fuera de orden |
| Ordenado por clave | Serializar todos los eventos para un ID; bloquear en caso de fallo | Bloqueo de cabeza de línea (head-of-line), menor rendimiento |
| Carga útil autoordenada | Incluir marca de tiempo + secuencia; el consumidor descarta lo obsoleto | Lógica del consumidor, pero sin bloqueo |

## Dead-letter y salud del endpoint

Un endpoint que permanece caído no debe consumir capacidad de reintento para siempre. Una vez agotada la política de reintentos, el evento se traslada a una cola dead-letter —un almacén duradero de eventos no entregables que un operador puede inspeccionar y reproducir cuando el endpoint se arregle— en lugar de descartarse en silencio. Para proteger ambos lados mientras un endpoint no está sano, los productores rastrean la salud de entrega por endpoint y aplican circuit-breaking: tras una racha sostenida de fallos el circuito se abre y los nuevos eventos se aparcan o fallan rápido en vez de intentarse, y luego una sonda semiabierta prueba periódicamente la recuperación antes de reanudar la entrega normal. Esto impide que un consumidor muerto genere un atasco de intentos condenados y permite que uno que se recupera vuelva a ramp up con elegancia. El comportamiento concreto de webhooks y reintentos de BroadNet está documentado para desarrolladores.

## Construir un consumidor idempotente

Como la entrega al menos una vez garantiza duplicados, es el consumidor —no el productor— quien es dueño de los efectos exactamente una vez. El patrón es un almacén de deduplicación indexado por un identificador de evento estable proporcionado por el productor (un Webhook-Id o id de evento, nunca el hash del payload por sí solo). Al recibir, el consumidor comprueba si ya procesó ese id; si es así, acusa con 2xx y no hace nada más; si no, registra el id y aplica el efecto en la misma transacción para que un fallo no pueda aplicar el efecto sin registrar el id. Combinado con payloads auto-ordenados, esto convierte un evento reentregado o fuera de orden en una operación nula, que es lo que transforma un transporte al menos una vez en resultados de negocio exactamente una vez.

*Consumidor idempotente (pseudocódigo)*

```text
on POST(event):
  verify_signature(event)          # reject if invalid
  id = event.headers['Webhook-Id']
  begin transaction:
    if seen(id): return 200        # duplicate -> no-op
    if event.sequence <= last_seq(event.message_id):
        mark_seen(id); return 200  # stale -> discard
    apply_effect(event)
    mark_seen(id); set_last_seq(...)
  commit
  return 200
```

## Preguntas frecuentes

### ¿Qué garantía de entrega ofrecen los webhooks?

Al menos una vez. El productor reintenta hasta ver un acuse 2xx, por lo que un consumidor puede recibir el mismo evento más de una vez. Exactamente una vez sobre HTTP plano exigiría una coordinación distribuida que el transporte no proporciona, así que se espera que el consumidor sea idempotente y deduplique sobre un id de evento estable.

### ¿Por qué añadir jitter al backoff de reintentos de webhooks?

Sin jitter, cada evento que falló durante una caída reintenta al unísono cuando el endpoint se recupera, creando una estampida sincronizada que puede volver a tumbarlo. El jitter completo —un retardo aleatorio entre cero y la cota de backoff actual— reparte los reintentos con suavidad para que el endpoint en recuperación vea un aumento gradual.

### ¿Cómo debe responder un endpoint de webhook?

Con rapidez y con un 2xx. El patrón recomendado es registrar de forma duradera el evento en bruto, devolver 200 de inmediato y procesar de forma asíncrona en un worker separado. Así una lógica de negocio lenta nunca provoca que salte el timeout de entrega del productor y dispare un reintento innecesario.

### ¿Se entregan los eventos de webhook en orden?

Por defecto solo en el mejor de los casos: los reintentos y la entrega en paralelo pueden entrelazarse, así que un evento anterior puede llegar después de uno posterior. Para obtener orden, o bien se serializa por clave (a costa de bloqueo de cabecera de línea) o bien se hace el payload auto-ordenado con una marca de tiempo y un número de secuencia para que el consumidor descarte las actualizaciones obsoletas.

### ¿Qué es una cola dead-letter para webhooks?

Un almacén duradero para eventos que no pudieron entregarse tras agotarse la política de reintentos. En lugar de descartarlos en silencio, el productor los aparca para que un operador pueda inspeccionar los fallos y reproducir los eventos una vez arreglado el endpoint del consumidor.

### ¿Por qué aplicar circuit-breaking a los endpoints de webhook?

Un endpoint que falla de forma persistente consumiría, si no, capacidad de reintento con intentos condenados y acumularía un atasco. El circuit-breaking se abre tras fallos sostenidos, aparcando o fallando rápido los nuevos eventos, y luego usa una sonda semiabierta para probar la recuperación antes de reanudar la entrega normal, protegiendo tanto al productor como al consumidor en recuperación.

## Fuentes

- [IETF RFC 9110 — HTTP Semantics (method properties, 202)](https://datatracker.ietf.org/doc/html/rfc9110)
- [IETF draft — Idempotency-Key HTTP Header Field](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header)

## Relacionado

- [Verificación de firma de webhooks](https://www.broadnet.me/es/reference/webhook-signature-verification)
- [Estado de mensajes y webhooks entrantes](https://www.broadnet.me/es/reference/message-status-and-inbound-webhooks)
- [Idempotencia y limitación de tasa](https://www.broadnet.me/es/reference/idempotency-and-rate-limiting)
- [Guía de webhooks de BroadNet](https://www.broadnet.me/es/developers/webhooks)
- [Reintentos y backoff de BroadNet](https://www.broadnet.me/developers/retries)
