30 mar 2025·8 min de lectura

Lista de verificación de fiabilidad de webhooks: reintentos, idempotencia y reproducción

Lista de verificación práctica para la fiabilidad de webhooks: reintentos, idempotencia, registros de reproducción y monitorización para webhooks entrantes y salientes cuando los partners fallan.

Lista de verificación de fiabilidad de webhooks: reintentos, idempotencia y reproducción

Por qué los webhooks parecen poco fiables en proyectos reales

Un webhook es un acuerdo simple: un sistema envía una petición HTTP a otro cuando ocurre algo. "Pedido enviado", "ticket actualizado", "dispositivo desconectado". Es básicamente una notificación push entre apps, entregada por la web.

En demos parecen fiables porque el camino feliz es rápido y limpio. En el trabajo real, los webhooks quedan entre sistemas que no controlas: CRM, proveedores de envío, centros de soporte, herramientas de marketing, plataformas IoT e incluso apps internas de otro equipo. Fuera de pagos, a menudo pierdes garantías maduras de entrega, esquemas de eventos estables y un comportamiento de reintentos consistente.

Las primeras señales suelen ser confusas:

  • Eventos duplicados (la misma actualización llega dos veces)
  • Eventos faltantes (algo cambió, pero nunca te enteraste)
  • Retrasos (una actualización llega minutos u horas después)
  • Eventos fuera de orden (un "cerrado" llega antes que "abierto")

Los sistemas de terceros inestables hacen que esto parezca aleatorio porque las fallas no siempre son ruidosas. Un proveedor puede agotar el tiempo pero aun así procesar tu petición. Un balanceador puede cortar la conexión después de que el emisor ya reintentó. O su sistema puede caer brevemente y luego enviar un aluvión de eventos antiguos de golpe.

Imagina un socio de envíos que manda webhooks de "entregado". Un día tu receptor está lento 3 segundos, así que reintentan. Recibes dos entregas, tu cliente recibe dos emails y soporte se confunde. Al día siguiente tienen una caída y no reintentan, así que el "entregado" nunca llega y tu dashboard se queda bloqueado.

La fiabilidad de los webhooks no trata de una petición perfecta sino de diseñar para la realidad desordenada: reintentos, idempotencia y la capacidad de reproducir y verificar lo ocurrido después.

Los tres bloques: reintentos, idempotencia, reproducción

Los webhooks van en dos direcciones. Los entrantes son llamadas que recibes de otro (un proveedor de pagos, CRM, herramienta de envíos). Los salientes son llamadas que envías a tu cliente o socio cuando algo cambia en tu sistema. Ambos pueden fallar por razones que no tienen nada que ver con tu código.

Los reintentos ocurren tras una falla. Un emisor puede reintentar porque recibió un timeout, un error 500, una conexión caída o ninguna respuesta rápida. Los buenos reintentos son comportamiento esperado, no un caso límite raro. La meta es que el evento pase sin inundar al receptor ni crear efectos duplicados.

La idempotencia es cómo haces seguros los duplicados. Significa "hazlo una vez, aunque llegue dos veces". Si el mismo webhook llega otra vez, lo detectas y devuelves una respuesta de éxito sin ejecutar la acción de negocio por segunda vez (por ejemplo, no crear una segunda factura).

La reproducción es tu botón de recuperación. Es la capacidad de reprocesar eventos pasados a propósito, de forma controlada, después de corregir un bug o tras la caída de un socio. La reproducción es diferente de los reintentos: los reintentos son automáticos e inmediatos; la reproducción es deliberada y suele ocurrir horas o días después.

Si quieres fiabilidad, plantea metas simples y diseña en torno a ellas:

  • No perder eventos (siempre puedes encontrar lo que llegó o lo que intentaste enviar)
  • Duplicados seguros (reintentos y reproducciones no cobran dos veces ni crean dos registros)
  • Rastro de auditoría claro (puedes responder "¿qué pasó?" rápido)

Una manera práctica de soportar los tres es almacenar cada intento de webhook con un estado y una clave de idempotencia única. Muchos equipos construyen esto como una pequeña tabla de "bandeja de entrada/salida" de webhooks.

Webhooks entrantes: un flujo receptor reutilizable

La mayoría de problemas con webhooks ocurren porque emisor y receptor van en relojes distintos. Tu trabajo como receptor es ser predecible: reconocer rápido, registrar lo que llegó y procesarlo de forma segura.

Separar "aceptar" de "hacer el trabajo"

Empieza con un flujo que mantenga la petición HTTP rápida y mueva el trabajo real a otro lugar. Esto reduce timeouts y hace que los reintentos sean mucho menos dolorosos.

  • Acknowledgement rápido. Devuelve un 2xx tan pronto como la petición sea aceptable.
  • Comprueba lo básico. Valida content-type, campos requeridos y parsing. Si el webhook está firmado, verifica la firma aquí.
  • Persiste el evento crudo. Guarda el body y los headers que necesitarás después (firma, event ID), junto con un timestamp de recepción y un estado como "recibido".
  • Encola el trabajo. Crea un job para procesamiento en segundo plano y luego devuelve tu 2xx.
  • Procesa con resultados claros. Marca el evento como "procesado" solo tras completar los efectos secundarios. Si falla, registra por qué y si debe reintentarse.

Cómo es "responder rápido"

Un objetivo realista es responder en menos de un segundo. Si el emisor espera un código concreto, úsalo (muchos aceptan 200, algunos prefieren 202). Devuelve 4xx solo cuando el emisor no deba reintentar (por ejemplo, firma inválida).

Ejemplo: llega un webhook "customer.created" mientras tu base de datos está bajo carga. Con este flujo, aún guardas el evento crudo, lo encolas y respondes 2xx. Tu worker podrá reintentar más tarde sin que el emisor tenga que reenviar.

Comprobaciones de seguridad que no rompen la entrega

Las comprobaciones de seguridad valen la pena, pero la meta es simple: bloquear tráfico malicioso sin bloquear eventos reales. Muchos problemas de entrega vienen de receptores demasiado estrictos o de devolver respuestas incorrectas.

Empieza por probar al emisor. Prefiere peticiones firmadas (header con HMAC) o un token compartido en un header. Verifícalo antes de hacer trabajo pesado y falla rápido si falta o es incorrecto.

Ten cuidado con los códigos de estado porque controlan los reintentos:

  • Devuelve 401/403 para fallos de auth para que el emisor no reintente indefinidamente.
  • Devuelve 400 para JSON malformado o campos requeridos faltantes.
  • Devuelve 5xx solo cuando tu servicio no puede aceptar o procesar temporalmente.

Las listas de IP pueden ayudar, pero solo si el proveedor tiene rangos de IP estables y documentados. Si cambian a menudo (o usan un gran pool en la nube), las allowlists pueden descartar webhooks reales silenciosamente y solo te enterarás mucho después.

Si el proveedor incluye un timestamp y un ID único de evento, puedes añadir protección contra reproducciones: rechaza mensajes demasiado antiguos y registra IDs recientes para detectar duplicados. Mantén la ventana de tiempo pequeña, pero permite un margen para que la deriva de reloj no rompa peticiones válidas.

Lista de comprobación de receptor amigable:

  • Valida firma o secreto compartido antes de parsear payloads grandes.
  • Aplicar un tamaño máximo de body y un timeout corto para la petición.
  • Usa 401/403 para fallos de auth, 400 para JSON malformado y 2xx para eventos aceptados.
  • Si verificas timestamps, permite una pequeña ventana de gracia (por ejemplo, unos minutos).

Para logging, guarda un rastro de auditoría sin conservar datos sensibles para siempre. Almacena event ID, nombre del emisor, hora de recepción, resultado de verificación y un hash del body crudo. Si debes guardar payloads, fija un periodo de retención y enmascara campos como emails, tokens o datos de pago.

Reintentos que ayudan, no dañan

Diseña reintentos con arrastrar y soltar
Crea flujos de reintentos y backoff de forma visual, sin cablear todo manualmente.
Comenzar a crear

Los reintentos son buenos cuando convierten un pequeño fallo en una entrega exitosa. Son dañinos cuando multiplican tráfico, ocultan bugs reales o crean duplicados. La diferencia es tener reglas claras sobre qué reintentar, cómo espaciar intentos y cuándo parar.

Como base, reintenta solo cuando el receptor probablemente tenga éxito más tarde. Un modelo mental útil: reintenta en fallos "temporales", no en "tuviste un error enviando algo".

Resultados HTTP prácticos:

  • Reintentar: timeouts de red, errores de conexión y HTTP 408, 429, 500, 502, 503, 504
  • No reintentar: HTTP 400, 401, 403, 404, 422
  • Depende: HTTP 409 (a veces "duplicado", a veces conflicto real)

El espaciado importa. Usa backoff exponencial con jitter para no provocar una tormenta de reintentos cuando muchos eventos fallen a la vez. Por ejemplo: esperar 5s, 15s, 45s, 2m, 5m y añadir un pequeño offset aleatorio cada vez.

También fija una ventana máxima de reintentos y un corte claro. Opciones comunes: "seguir intentando hasta 24 horas" o "no más de 10 intentos". Después de eso, trátalo como un problema de recuperación, no de entrega.

Para que esto funcione en el día a día, tu registro de eventos debería capturar:

  • Cuenta de intentos
  • Último error
  • Próxima hora de intento
  • Estado final (incluyendo un estado dead-letter cuando dejas de reintentar)

Los items en dead-letter deben ser fáciles de inspeccionar y seguros de reproducir después de arreglar el problema subyacente.

Patrones de idempotencia que funcionan en la práctica

Idempotencia significa que puedes procesar el mismo webhook más de una vez sin crear efectos secundarios extra. Es una de las mejoras más rápidas para la fiabilidad, porque los reintentos y timeouts ocurrirán aunque nadie esté haciendo nada mal.

Elige una clave que sea estable

Si el proveedor te da un event ID, úsalo. Es la opción más limpia.

Si no hay event ID, construye tu propia clave a partir de campos estables que tengas, como un hash de:

  • proveedor + tipo de evento + ID de recurso + timestamp, o
  • proveedor + message ID

Guarda la clave con un pequeño conjunto de metadatos (hora de recepción, proveedor, tipo de evento y el resultado).

Reglas que suelen funcionar:

  • Trata la clave como obligatoria. Si no puedes construirla, pon el evento en cuarentena en vez de adivinar.
  • Guarda claves con TTL (por ejemplo 7 a 30 días) para que la tabla no crezca sin control.
  • Almacena también el resultado del procesamiento (éxito, fallado, ignorado) para que los duplicados obtengan una respuesta consistente.
  • Pon una restricción única en la clave para que dos peticiones paralelas no ejecuten la misma acción.

Haz también idempotente la acción de negocio

Incluso con una tabla de claves, tus operaciones reales deben ser seguras. Ejemplo: un webhook "crear pedido" no debería crear un segundo pedido si el primer intento agotó tiempo después del insert en la base. Usa identificadores naturales de negocio (external_order_id, external_user_id) y patrones upsert.

Los eventos fuera de orden son comunes. Si recibes "user_updated" antes de "user_created", decide una regla como "solo aplicar cambios si event_version es más nuevo" o "solo actualizar si updated_at es posterior a lo que tenemos".

Los duplicados con payloads diferentes son el caso más difícil. Decide de antemano qué hacer:

  • Si la clave coincide pero el payload difiere, trátalo como un bug del proveedor y alerta.
  • Si la clave coincide y el payload solo difiere en campos irrelevantes, ignóralo.
  • Si no puedes confiar en el proveedor, cambia a una clave derivada del hash completo del payload y maneja conflictos como eventos nuevos.

La meta es simple: un cambio en el mundo real debe producir un resultado en el mundo real, aunque veas el mensaje tres veces.

Herramientas de reproducción y registros de auditoría para recuperar

De no-code a código real
Genera código listo para producción que puedes desplegar en la nube o exportar para autoalojarlo.
Probar ahora

Cuando un sistema socio es inestable, la fiabilidad es menos entrega perfecta y más recuperación rápida. Una herramienta de reproducción convierte "perdimos algunos eventos" en una reparación rutinaria en lugar de una crisis.

Empieza con un log de eventos que siga el ciclo de vida de cada webhook: recibido, procesado, fallado o ignorado. Manténlo buscable por tiempo, tipo de evento e ID de correlación para que soporte responda "¿qué pasó con el pedido 18432?" rápidamente.

Para cada evento, guarda el contexto suficiente para ejecutar la misma decisión más tarde:

  • Payload crudo y headers clave (firma, event ID, timestamp)
  • Campos normalizados que extrajiste
  • Resultado del procesamiento y mensaje de error (si hay)
  • Versión del flujo o mapping usado en ese momento
  • Timestamps de recepción, inicio y fin

Con eso, añade una acción "Reproducir" para eventos fallidos. El botón es menos importante que las guardas. Un buen flujo de reproducción muestra el error previo, qué ocurrirá al reproducir y si el evento es seguro de volver a ejecutar.

Guardas que previenen daños accidentales:

  • Requerir una nota de motivo antes de reproducir
  • Restringir permisos de reproducción a un rol pequeño
  • Volver a ejecutar las mismas comprobaciones de idempotencia que en el primer intento
  • Limitar la tasa de repeticiones para evitar un pico nuevo durante incidentes
  • Modo dry-run opcional que valida sin escribir cambios

Los incidentes suelen implicar más de un evento, así que soporta reproducir por rango de tiempo (por ejemplo, "reproducir todos los eventos fallidos entre 10:05 y 10:40"). Registra quién reprodujo qué, cuándo y por qué.

Webhooks salientes: un flujo emisor que puedas auditar

Maneja la seguridad sin romper entregas
Implementa comprobaciones de firma y reglas de códigos de estado sin bloquear entregas reales.
Probar AppMaster

Los webhooks salientes fallan por razones aburridas: un receptor lento, una caída breve, un problema DNS o un proxy que corta peticiones largas. La fiabilidad viene de tratar cada envío como un trabajo registrado y repetible, no como una llamada HTTP puntual.

Un flujo emisor que se mantenga predecible

Da a cada evento un ID único y estable. Ese ID debe mantenerse igual entre reintentos, reproducciones e incluso reinicios del servicio. Si generas un ID nuevo por intento, haces la deduplicación más difícil para el receptor y el seguimiento más complicado para ti.

Luego, firma cada petición e incluye un timestamp. El timestamp ayuda a los receptores a rechazar solicitudes muy antiguas y la firma prueba que el payload no se modificó en tránsito. Mantén las reglas de firma simples y consistentes para que los partners las implementen sin adivinar.

Haz seguimiento de entregas por endpoint, no solo por evento. Si envías el mismo evento a tres clientes, cada destino necesita su historial de intentos y su estado final.

Un flujo práctico que la mayoría puede implementar:

  • Crea un registro de evento con event ID, endpoint ID, hash del payload y estado inicial.
  • Envía la petición HTTP con firma, timestamp y un header de clave de idempotencia.
  • Registra cada intento (hora de inicio, fin, estado HTTP, mensaje corto de error).
  • Reintenta solo en timeouts y respuestas 5xx, usando backoff exponencial con jitter.
  • Para tras un límite claro (max intentos o antigüedad máxima) y márcalo como fallido para revisión.

Ese header de clave de idempotencia importa incluso cuando tú eres el emisor. Le da al receptor una forma limpia de deduplicar si procesaron la primera petición pero tu cliente no recibió la respuesta 200.

Finalmente, haz visibles los fallos. "Fallado" no debería significar "perdido". Debe significar "pausado con contexto suficiente para reproducir de forma segura".

Ejemplo: un socio inestable y una recuperación limpia

Tu app de soporte envía actualizaciones de tickets a un sistema partner para que sus agentes vean el mismo estado. Cada vez que cambia un ticket (asignado, prioridad actualizada, cerrado), publicas un webhook como ticket.updated.

Una tarde el endpoint del partner empieza a agotar tiempo. Tu primer intento espera, llega al timeout de tu cliente y lo tratas como "desconocido" (quizá les llegó, quizá no). Una buena estrategia de reintentos reintenta con backoff en vez de disparar repetidos cada segundo. El evento queda en cola con el mismo event ID y cada intento queda registrado.

La parte dolorosa: si no usas idempotencia, el partner puede procesar duplicados. El intento #1 pudo haberles llegado pero su respuesta nunca te retornó. El intento #2 llega después y crea un segundo "Ticket cerrado", enviando dos emails o creando dos entradas en la línea de tiempo.

Con idempotencia, cada entrega incluye una clave derivada del evento (a menudo el event ID). El partner guarda esa clave por un periodo y responde "ya procesado" para repeticiones. Dejas de adivinar.

Cuando el partner vuelve, la reproducción arregla la única actualización que realmente faltó (por ejemplo, un cambio de prioridad durante la caída). Seleccionas el evento en tu registro y lo reproduces una vez, con el mismo payload y la misma clave de idempotencia, así es seguro aunque ya lo hayan recibido.

Durante el incidente, tus logs deben contar la historia claramente:

  • Event ID, ticket ID, tipo de evento y versión del payload
  • Número de intento, timestamps y próxima hora de retry
  • Timeout vs respuesta no-2xx vs éxito
  • Clave de idempotencia enviada y si el partner reportó "duplicate"
  • Un registro de reproducción mostrando quién la ejecutó y el resultado final

Errores comunes y trampas a evitar

Monitorea fallos en un solo lugar
Construye un dashboard operativo para rastrear eventos en dead-letter y recuperarte rápido.
Crear proyecto

La mayoría de incidentes con webhooks no vienen de un gran bug, sino de pequeñas decisiones que rompen la fiabilidad cuando el tráfico sube o un tercero falla.

Las trampas que aparecen en postmortems:

  • Hacer trabajo lento dentro del handler de la petición (escrituras DB, llamadas a APIs, subidas de archivos) hasta que el emisor agota tiempo y reintenta
  • Asumir que los proveedores nunca envían duplicados y luego cobrar dos veces, crear pedidos duplicados o enviar dos correos
  • Devolver códigos de estado equivocados (200 aun cuando no aceptaste el evento, o 500 por datos inválidos que nunca tendrán éxito con reintentos)
  • Lanzar sin un ID de correlación, event ID o request ID y luego pasar horas correlacionando logs con reportes de clientes
  • Reintentar sin límite, lo que construye backlog y convierte una caída de un partner en tu propia caída

Una regla simple funciona: acknowledge rápido y luego procesa de forma segura. Valida solo lo necesario para decidir si aceptar el evento, guárdalo y haz el resto asíncrono.

Los códigos de estado importan más de lo que se piensa:

  • Usa 2xx solo cuando hayas almacenado el evento (o lo hayas encolado) y confíes en que será manejado.
  • Usa 4xx para input inválido o auth fallida para que el emisor deje de reintentar.
  • Usa 5xx solo para problemas temporales de tu lado.

Fija un techo de reintentos. Para después de una ventana fija (por ejemplo 24 horas) o un número fijo de intentos y marca el evento como "necesita revisión" para que un humano decida qué reproducir.

Checklist rápido y siguientes pasos

La fiabilidad de webhooks es sobre hábitos repetibles: aceptar rápido, deduplicar agresivamente, reintentar con cuidado y mantener una ruta de reproducción.

Comprobaciones rápidas para entrantes (receptor)

  • Devuelve un 2xx rápido una vez que la petición esté guardada de forma segura (haz trabajo lento en async).
  • Guarda suficiente del evento para probar lo que recibiste (y depurar luego).
  • Requiere una clave de idempotencia (o deriva una de proveedor + event ID) y hazla cumplir en la base de datos.
  • Usa 4xx para firma inválida o esquema inválido, y 5xx solo para problemas reales del servidor.
  • Rastrea estado de procesamiento (recibido, procesado, fallado) y el último mensaje de error.

Comprobaciones rápidas para salientes (emisor)

  • Asigna un event ID único por evento y mantenlo estable entre intentos.
  • Firma cada petición e incluye un timestamp.
  • Define una política de reintentos (backoff, max intentos y cuándo detenerse) y cúmplela.
  • Rastrea estado por endpoint: último éxito, último fallo, fallos consecutivos, próxima hora de reintento.
  • Registra cada intento con detalle suficiente para soporte y auditoría.

Para ops, decide de antemano qué vas a reproducir (evento individual, lote por rango de tiempo/estado o ambos), quién puede hacerlo y cómo será tu rutina de revisión de dead-letter.

Si quieres construir estas piezas sin cablear todo manualmente, una plataforma no-code como AppMaster (appmaster.io) puede encajar bien: puedes modelar tablas de bandeja de entrada/salida en PostgreSQL, implementar flujos de reintentos y reproducción en un Business Process Editor visual y desplegar un panel interno para buscar y volver a ejecutar eventos fallidos cuando los partners se vuelvan inestables.

FAQ

Why do webhooks feel reliable in demos but break in real projects?

Los webhooks están entre sistemas que no controlas, por eso heredas sus timeouts, caídas, reintentos y cambios de esquema. Aunque tu código sea correcto, puedes ver duplicados, eventos perdidos, retrasos y entregas fuera de orden.

What’s the simplest way to make inbound webhooks reliable?

Diseña pensando en reintentos y duplicados desde el primer día. Guarda cada evento entrante, responde con un 2xx rápido una vez que esté registrado de forma segura y procésalo de forma asíncrona con una clave de idempotencia para que entregas repetidas no repitan efectos secundarios.

How fast should my webhook endpoint respond?

Debes confirmar rápido tras la validación y el almacenamiento básico, normalmente en menos de un segundo. Si haces trabajo lento dentro de la petición, los emisores agotan tiempo y reintentan, lo que aumenta duplicados y complica incidentes.

What does idempotency mean for webhooks in plain terms?

Idempotencia significa “hacer la acción de negocio una vez, aunque el mensaje llegue varias veces”. Se aplica usando una clave de idempotencia estable (a menudo el event ID que da el proveedor), guardándola y devolviendo éxito para duplicados sin ejecutar la acción otra vez.

What should I use as an idempotency key if the provider doesn’t give an event ID?

Usa el event ID del proveedor si existe. Si no, deriva una clave a partir de campos estables y evita campos que cambien entre reintentos. Si no puedes construir una clave estable, pon el evento en cuarentena para revisión en lugar de adivinar.

Which HTTP status codes should I return so retries behave correctly?

Devuelve 4xx para problemas que el emisor no puede arreglar con un reintento (por ejemplo, autenticación fallida o payload mal formado). Usa 5xx solo para problemas temporales en tu lado. Sé consistente: el código de estado suele determinar si el emisor reintentará.

What’s a safe retry policy for outbound webhooks?

Reintenta en timeouts, errores de conexión y respuestas temporales como 408, 429 y 5xx. Usa backoff exponencial con jitter y un límite claro (por ejemplo, número máximo de intentos o antigüedad máxima), luego mueve el evento a “necesita revisión”.

What’s the difference between retries and replay?

La reproducción es volver a procesar deliberadamente eventos pasados tras arreglar un bug o recuperarse de una caída. Los reintentos son automáticos e inmediatos. Una buena reproducción requiere un log de eventos, comprobaciones de idempotencia seguras y guardas para no duplicar trabajo accidentalmente.

How do I handle out-of-order webhook events like “closed” arriving before “opened”?

Asume que recibirás eventos fuera de orden y define una regla que cuadre con tu dominio. Un enfoque común es aplicar actualizaciones solo si la versión del evento o su timestamp es más reciente que lo que tienes almacenado, para que llegadas tardías no sobrescriban el estado actual.

How can I implement an audit trail and replay tool without building everything from scratch?

Construye una tabla simple de bandeja de entrada/salida y una vista administrativa para buscar, inspeccionar y reproducir eventos fallidos. En AppMaster (appmaster.io) puedes modelar estas tablas en PostgreSQL, implementar deduplicación, reintentos y flujos de reproducción en el Business Process Editor, y desplegar un panel interno para soporte sin codificar todo a mano.

Fácil de empezar
Crea algo sorprendente

Experimente con AppMaster con plan gratuito.
Cuando esté listo, puede elegir la suscripción adecuada.

Empieza