Webhooks: repeticiones y recibos
1) Modelo de envío básico
At-least-once (predeterminado): el evento se entregará ≥1 vez. Las garantías son alcanzadas suavemente-una-vez por la idempotencia del receptor.
Recibo (ACK): sólo cualquier 2xx (normalmente 200/204) del destinatario significa éxito. Todo lo demás se interpreta como rechazo y conduce a la repetición.
ACK rápido: responda 2xx después de colocar el evento a su vez, no después del procesamiento empresarial completo.
2) Formato de eventos y títulos obligatorios
Carga útil (ejemplo)
json
{
"id": "evt_01HXYZ",
"type": "order. created",
"occurred_at": "2025-11-03T18:10:12Z",
"sequence": 128374,
"source": "orders",
"data": { "order_id": "o_123", "amount": "49. 90", "currency": "EUR" },
"schema_version": 1
}
Encabezados del remitente
'X-Webhook-Id: evt_01HXYZ' es un ID de evento único (use para deduplicación).
'X-Webhook-Seq: 128374' es una secuencia monótona (por suscripción/tema).
`X-Signature: sha256=<base64(hmac_sha256(body, secret))>` — HMAC-подпись.
'X-Retry: 0,1,2...' es el número de intento.
'X-Webhook-Versión: 1' - versionar el contrato.
(opcional) 'Traceparent' es una correlación de pistas.
Respuesta
2xx - aceptado con éxito (no habrá más repeticiones de este 'id').
410 Gone - endpoint está eliminado/inactivo → el remitente detiene las repeticiones y desactiva la suscripción.
429/5xx/timaut - el remitente repite por política de retraídas.
3) Política de repetición (retries)
Escalera recomendada backoff (+ jitter)
'1s, 3s, 10s, 30s, 2m, 10m, 30m, 2h, 6h, 24h' (detenemos después del límite, por ejemplo 48-72 horas).
Reglas:- Backoff exponencial + jitter aleatorio (± 20-30%) para evitar el «efecto manada».
- Quórum de errores para fallas temporales (por ejemplo, repetición si 5xx o tiempo de espera de red).
- Respuesta 429: ponga un mínimo de 'min (título Retry-After, siguiente ventana backoff)'.
Tamaños y tamaños
Tiempo de espera de conexión ≤ 3-5 segundos; tiempo de respuesta total ≤ 10 segundos.
El tamaño del cuerpo por contrato (por ejemplo, ≤ 256 KB), de lo contrario 413 → la lógica «chunking» o «pull URL».
4) Idempotencia y deduplicación
Aplicación idempotente: el procesamiento de repeticiones del mismo 'id' debe devolver el mismo resultado y no cambiar el estado de nuevo.
Almacenamiento de información en el lado del destinatario: almacenar '(X-Webhook-Id, processed_at, checksum)' con TTL ≥ ventanas retraídas (24-72 h).
Clave compositiva: si varios topics → '(subscription_id, event_id)'.
5) Orden y «exactly-once efectos»
Es difícil garantizar un orden estricto en los sistemas distribuidos. Utilice:- Partition by key: el mismo conjunto lógico (por ejemplo, 'order _ id') siempre está en el mismo «canal» de entrega.
- Sequence: rechaza los eventos con el antiguo 'X-Webhook-Seq' y póngalos en un 'parking lot' antes de que lleguen los que faltan.
- registro de las operaciones aplicadas (outbox/inbox pattern),
- upsert transaccional por 'event _ id' en DB,
- sagas/compensaciones para procesos complejos.
6) Solución de errores de código de estado (tabla)
7) Seguridad del canal
Firma HMAC de cada mensaje; comprobación en el receptor con «ventana de tiempo» (mitm y replay-ataque).
mTLS para dominios sensibles (CUS/pagos).
IP allowlist de direcciones salientes, TLS 1. 2+, HSTS.
Minimización PII: no envíe datos personales superfluos; enmascarar en las guarniciones.
Rotación de secretos: dos claves activas (active/next) y un encabezado 'X-Key-Id' para indicar la actual.
8) Colas, DLQ y réplicas
Los eventos se escriben necesariamente en una cola de salida/registro en el lado del remitente (para una reproducción confiable).
Cuando se supera el máximo de retraídas, el evento se va al DLQ (Dead Letter Queue) con la causa.
Replay API (para destinatario/operador): volver a enviar por 'id '/intervalo de tiempo/tema, con límite de RPS y firma/autorización adicional.
POST /v1/webhooks/replay
{ "subscription_id": "sub_123", "from": "2025-11-03T00:00:00Z", "to": "2025-11-03T12:00:00Z" }
→ 202 Accepted
9) Contrato y versión
Versione el evento (campo 'schema _ version') y el transporte ('X-Webhook-Version').
Agregue campos sólo como opcionales; cuando se elimina, migración menor y período de transición (dual-write).
Documente tipos de eventos, ejemplos, esquemas (JSON Schema), códigos de error.
10) Observabilidad y SLO
Métricas clave del remitente:- 'delivery _ success _ rate' (2xx/todos los intentos), 'first _ attempt _ success _ rate'
- `retries_total`, `max_retry_age_seconds`, `dlq_count`
- `latency_p50/p95` (occurred_at → ack_received_at)
- `ack_latency` (receive → 2xx), `processing_latency` (enqueue → done)
- `duplicates_total`, `invalid_signature_total`, `out_of_order_total`
99. El 9% de los eventos reciben el primer ACK ≤ 60 segundos (28d).
- DLQ ≤ 0. 1% del total; réplica DLQ ≤ 24 h.
11) Tiempo de espera y roturas de red
Utilice UTC en los campos de tiempo; sincronizar NTP.
Envíe 'occurred _ at' y confirme 'delivered _ at' para contar el trago.
Con interrupciones prolongadas, la red/endpoint → acumular en la cola, limitar el crecimiento (backpressure + cuotas).
12) Límites recomendados e higiene
RPS de suscripción (por ejemplo, 50 RPS, burst 100) + paralelismo (por ejemplo, 10).
Max. cuerpo: 64-256 KB; para más - «notification + URL» y la firma de descarga.
Nombres de eventos en 'snake. case 'o' dot. type` (`order. created`).
Idempotencia estricta de las operaciones de escritura del receptor.
13) Ejemplos: remitente y destinatario
13. 1 Remitente (pseudocódigo)
python def send_event(event, attempt=0):
body = json. dumps(event)
sig = hmac_sha256_base64(body, secret)
headers = {
"X-Webhook-Id": event["id"],
"X-Webhook-Seq": str(event["sequence"]),
"X-Retry": str(attempt),
"X-Signature": f"sha256={sig}",
"Content-Type": "application/json"
}
res = http. post(endpoint, body, headers, timeout=10)
if 200 <= res. status < 300:
mark_delivered(event["id"])
elif res. status == 410:
deactivate_subscription()
else:
schedule_retry(event, attempt+1) # backoff + jitter, respect 429 Retry-After
13. 2 Destinatario (pseudocódigo)
python
@app. post("/webhooks")
def handle():
body = request. data headers = request. headers assert verify_hmac(body, headers["X-Signature"], secret)
evt_id = headers["X-Webhook-Id"]
if dedup_store. exists(evt_id):
return, "" 204 enqueue_for_processing (body) # fast path. dedup_store put(evt_id, ttl=723600)
return, "" 202 # or 204
14) Pruebas y prácticas de caos
Casos negativos: firma invisible, 429/5xx, timaut, 410, grandes payload's.
Conductual: fuera de orden, duplicados, retrasos de 1-10 minutos, brecha de 24 horas.
Carga: burst 10 ×; compruebe la backpressure y la estabilidad de DLQ.
Contratos: JSON Schema, títulos obligatorios, tipos de eventos estables.
15) Lista de verificación de implementación
- 2xx = ACK, y un retorno rápido después de enqueue
- Exponencial backoff + jitter, respeto 'Retry-After'
- Idempotencia del receptor y del dedoop por 'X-Webhook-Id' (TTL ≥ retrae)
- Firmas HMAC, rotación de secretos, optional mTLS
- DLQ + Replay API, monitoreo y alertas
- Restricciones: temporizadores, RPS, tamaño del cuerpo
- Orden: partition by key o 'sequence' + «parking lot»
- Documentación: esquemas, ejemplos, codificaciones de errores, versiones
- Pruebas de caos: retrasos, tomas, fallo de red, replay prolongado
16) Mini preguntas frecuentes
¿Siempre tienes que responder 200?
Cualquier 2xx se cuenta como un éxito. 202/204 es una práctica normal para «aceptada en la cola».
¿Es posible detener las repeticiones?
Sí, respuesta 410 y/o a través de la consola/API del remitente (desconexión de suscripción).
¿Qué pasa con los grandes payload 'ami?
Envía una «notificación + URL segura», firma la solicitud de descarga e instala la TTL.
¿Cómo puedo mantener el orden?
Partition by key + `sequence`; en caso de discrepancia, «parking lot» y rejuvenecimiento.
Resultado
Los webhooks confiables son una clara semántica ACK (2xx), repeticiones razonables con backoff + jitter, idempotencia y deduplicación rigurosas, seguridad competente (HMAC/mTLS), colas + DLQ + réplicas, y observabilidad transparente. Fije el contrato, introduzca límites y métricas, ejecute los escenarios de caos con regularidad y sus integraciones dejarán de «saltar» cuando se produzcan los primeros fallos.