Aller au contenu

Idempotence des Webhooks

Sangho garantit une livraison at-least-once : un événement peut être livré plusieurs fois en cas d’échec réseau, de timeout ou de redémarrage de votre serveur. Votre endpoint doit être idempotent — traiter deux fois le même événement ne doit pas produire d’effet de bord supplémentaire.

Stratégie de déduplication

  • 1. Extraire le Sangho-Event-ID du header (ou event.id du body).
  • 2. Vérifier en base si cet ID a déjà été traité.
  • 3. Si oui → retourner 200 immédiatement sans retraiter.
  • 4. Si non → traiter l'événement, puis enregistrer l'ID.
Stockez les IDs en base de données

N’utilisez pas un Set en mémoire en production — il se réinitialise au redémarrage du serveur. Utilisez une table SQL avec une contrainte PRIMARY KEY sur l’event_id, ou Redis avec SET NX (SET if Not eXists).

INSERT ... ON CONFLICT DO NOTHING

En PostgreSQL et SQLite, utilisez INSERT … ON CONFLICT (event_id) DO NOTHING pour une déduplication atomique sans race condition, même sous charge concurrente.

Déduplication (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 });
  },
);

Modèle SQL de déduplication

Créez une table dédiée pour stocker les IDs d’événements déjà traités. La contrainte PRIMARY KEY sur event_id garantit l’unicité au niveau base de données, même en cas de requêtes concurrentes.

Ajoutez un index sur processed_at pour faciliter le nettoyage périodique des anciens enregistrements (après 30 jours par exemple).

Durée de rétention

Sangho ne relivrera jamais un événement après 48 heures. Vous pouvez supprimer les enregistrements de déduplication de plus de 72 heures sans risque. Planifiez un job de nettoyage via Celery Beat, cron, ou une tâche planifiée.

Schéma 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';