Ingeniería

Idempotencia y resiliencia en webhooks: procesando miles de eventos sin duplicados.

Tolerancia a fallos en la integración de pasarelas de pago y servicios SaaS: claves de idempotencia, deduplicación en cola y arquitectura de reconexión automática.

Paneles de vidrio esmerilado con verificación de idempotencia y enlaces de luz

La promesa no garantizada del "At-Least-Once Delivery"

Todos los proveedores serios de infraestructura (Stripe, WhatsApp Cloud API, Shopify, Mercado Pago) envían notificaciones mediante webhooks bajo la garantía de "al menos una vez" (at-least-once). Esto significa que debido a latencias de red o reintentos de servidor, tu endpoint recibirá el mismo evento dos, tres o diez veces.

El desastre de no ser idempotente

Si tu servidor procesa un evento de pago o alta de usuario sin verificar si ya lo ejecutó, los resultados son catastróficos: créditos cobrados doblemente, múltiples correos de bienvenida enviados al mismo cliente o registros duplicados en la base de datos.

La arquitectura de recepción idempotente en Netika Labs

En Netika organizamos la recepción de webhooks en 3 capas desacopladas:

  1. Respuesta inmediata HTTP 200 OK: El webhook valida la firma criptográfica (HMAC-SHA256) y responde 200 OK en menos de 50ms para evitar que el emisor asuma un timeout y vuelva a enviar.
  2. Deduplicación en memoria atómica: Se registra la clave de idempotencia (usualmente event.id) en Redis o SQLite con una expiración (TTL) de 24 horas. Si el ID ya existe, se ignora el evento silenciosamente.
  3. Procesamiento en cola asíncrona: El trabajo pesado se envía a una cola de tareas (Worker Queue) para su ejecución en segundo plano.
// Pseudocódigo de receptor de Webhook en Netika Labs
app.post("/webhook/stripe", async (req, res) => {
  const sig = req.headers["stripe-signature"];
  const event = verifySignature(req.body, sig, SECRET);

  // Verificación de idempotencia
  const isDuplicate = await redis.set(`evt:${event.id}`, "1", "NX", "EX", 86400);
  if (!isDuplicate) {
    return res.status(200).send({ received: true, status: "duplicate_ignored" });
  }

  // Encolar trabajo de fondo
  await jobQueue.add("process_event", event);
  return res.status(200).send({ received: true });
});

Manejo de reordenamiento de eventos

Otro problema común es la llegada fuera de orden: un evento de "cancelación de suscripción" llega antes que el evento de "creación de suscripción". Resolvemos esto comparando estampas de tiempo absolutas (timestamps) en la entidad de base de datos antes de actualizar el estado.

¿Tu sistema sufre de eventos de webhook duplicados o perdidos?

Diseñamos arquitecturas de integración resilientes e idempotentes a prueba de fallos.

Hablemos