REJCOB
← Todo el trabajo

Kit de ingesta confiable de webhooks

Una capa pequeña y reutilizable que vuelve confiables los webhooks de terceros: nada se pierde, nada se procesa dos veces, todo se puede reproducir.

Rol
Arquitectura y diseño
Período
2026
Empresa
Proyecto personal

Persist-first

guardar el evento crudo, luego responder 200

Dedupe

por ID de evento del proveedor, forzado por clave única

Replay

cualquier evento o rango de tiempo, bajo demanda

#Contexto

Los proveedores de pagos, mensajería y telefonía entregan cambios de estado por webhooks, y todos se comportan igual: reintentan, entregan fuera de orden, envían duplicados, y dan timeout si el handler es lento. Las integraciones que hacen el trabajo dentro del handler HTTP terminan perdiendo o aplicando dos veces algún evento. Este kit aísla ese problema una sola vez para que cada integración nueva solo escriba su handler.

#Problema y restricciones

  • Procesar dentro del request significa que una llamada lenta aguas abajo se vuelve un timeout del proveedor y una tormenta de reintentos.
  • Los proveedores garantizan entrega al-menos-una-vez, así que los handlers ven duplicados salvo que algo los elimine.
  • Cuando sale un bug en un handler, los eventos afectados se pierden salvo que se hayan guardado los payloads crudos.

#Arquitectura

ProviderwebhookEndpointverify · insert · 200Inbox tableunique event IDBullMQbackoff + jitterHandleridempotentDead-lettererror attachedReplay CLIevent · DLQ · range
Los eventos se verifican y guardan antes del 200; el procesamiento, los reintentos y el replay ocurren detrás del inbox.

El endpoint HTTP hace tres cosas y nada más: verificar la firma del proveedor contra el body crudo, insertar el evento crudo en una tabla inbox de PostgreSQL con clave por proveedor e ID de evento, y responder 200. La clave única convierte una entrega duplicada en un no-op. Un relay toma las filas nuevas del inbox y las encola en BullMQ; los workers ejecutan el handler específico de la integración con backoff exponencial y jitter. Un job que agota sus intentos pasa a una cola dead-letter con el error adjunto, y la fila del inbox registra el estado de cada intento. Como se guardan el payload crudo y los headers, un CLI puede reproducir un solo evento, todo lo que está en la cola dead-letter, o un rango de tiempo — que es también como un handler corregido se aplica a los eventos en los que antes falló. Los handlers se escriben idempotentes, con el ID de evento disponible como clave natural.

#Decisiones clave y tradeoffs

01Inbox en Postgres antes de la cola

Decisión
Escribir el evento primero en una tabla durable y encolar desde ahí, en vez de empujar directo a Redis.
Alternativas
Encolar directamente desde el handler HTTP, o procesar de forma síncrona.
Por qué
El inbox es el sistema de registro: sobrevive a una caída de la cola, da deduplicación con una simple restricción única, y hace del replay una consulta.
Costo de equivocarse
Una escritura extra por evento y un proceso relay que operar; la tabla necesita una política de retención.

02Cola dead-letter con replay

Decisión
Apartar los eventos fallidos con su error tras reintentos acotados, y hacer que volver a ejecutarlos sea un comando de primera clase.
Alternativas
Reintentar para siempre, o descartar tras el último intento y depender de logs.
Por qué
Los reintentos infinitos esconden bugs reales y bloquean la cola; descartar pierde datos. Un estacionamiento visible y reproducible convierte un incidente en un fix más un comando.
Costo de equivocarse
Alguien tiene que vigilar la cola dead-letter; sin una alerta se vuelve un backlog silencioso.

#Resultados

At-least-once

entrega convertida en procesamiento efectivamente-una-vez

< 1 handler

de trabajo en el camino del request: verificar, insertar, confirmar

1 command

para reproducir un evento, la DLQ o un rango de tiempo

#Qué haría diferente

Lo que este diseño no resuelve es el orden: dos eventos de la misma entidad pueden procesarse fuera de secuencia. El siguiente paso son claves de orden por entidad en la cola y una prueba de caos — workers caídos, entregas duplicadas y reordenadas — para ponerle números al throughput y confirmar que nada se pierde.

#Stack

Node.jsTypeScriptPostgreSQLBullMQRedisDockerWebhooks