Ir al contenido

Idempotencia de los Webhooks

Sangho garantiza una entrega at-least-once (al menos una vez): un evento puede entregarse varias veces en caso de fallo de red, de timeout o de reinicio de su servidor. Su endpoint debe ser idempotente: procesar dos veces el mismo evento no debe producir ningún efecto secundario adicional.

Estrategia de deduplicación

  • 1. Extraer el Sangho-Event-ID de la cabecera (o event.id del cuerpo).
  • 2. Comprobar en la base de datos si ese ID ya se ha procesado.
  • 3. Si es así → devolver 200 de inmediato sin volver a procesarlo.
  • 4. Si no → procesar el evento y registrar después el ID.
Almacene los ID en la base de datos

No utilice un Set en memoria en producción: se reinicia al reiniciar el servidor. Utilice una tabla SQL con una restricción PRIMARY KEY sobre event_id, o Redis con SET NX (SET if Not eXists).

INSERT ... ON CONFLICT DO NOTHING

En PostgreSQL y SQLite, utilice INSERT … ON CONFLICT (event_id) DO NOTHING para una deduplicación atómica sin condiciones de carrera, incluso con carga concurrente.

Deduplicación (Node.js)

javascript
// En production : remplacer processedEvents par Redis ou une table DB
const processedEvents = new Set<string>();


app.post(
  '/webhook',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const event = sangho.webhooks.constructEvent(
      req.body,
      req.headers['sangho-signature'] as string,
      process.env.WEBHOOK_SECRET!,
    );


    // Vérifier si l'événement a déjà été traité
    if (processedEvents.has(event.id)) {
      console.log('Doublon ignoré :', event.id);
      return res.json({ received: true });
    }


    // Traiter l'événement
    await handleEvent(event);


    // Marquer comme traité
    processedEvents.add(event.id);


    res.json({ received: true });
  },
);

Modelo SQL de deduplicación

Cree una tabla específica para almacenar los ID de los eventos ya procesados. La restricción PRIMARY KEY sobre event_id garantiza la unicidad a nivel de base de datos, incluso con solicitudes concurrentes.

Añada un índice sobre processed_at para facilitar la limpieza periódica de los registros antiguos (por ejemplo, tras 30 días).

Período de retención

Sangho no volverá a entregar nunca un evento pasadas 48 horas. Puede eliminar sin riesgo los registros de deduplicación con más de 72 horas. Programe una tarea de limpieza mediante Celery Beat, cron o una tarea programada.

Esquema SQL

sql
-- PostgreSQL / SQLite
CREATE TABLE processed_webhook_events (
  event_id     VARCHAR(50)  PRIMARY KEY,
  event_type   VARCHAR(100) NOT NULL,
  processed_at TIMESTAMP    DEFAULT NOW()
);


-- Index pour le nettoyage automatique
CREATE INDEX idx_webhook_processed_at
  ON processed_webhook_events (processed_at);


-- Insertion idempotente (atomique, sans race condition)
INSERT INTO processed_webhook_events (event_id, event_type)
VALUES ('evt_xxxx', 'payment_intent.succeeded')
ON CONFLICT (event_id) DO NOTHING;


-- Nettoyage des anciens enregistrements (> 72h)
DELETE FROM processed_webhook_events
WHERE processed_at < NOW() - INTERVAL '72 hours';