Événements webhook

Je travaille sur un flux de production où une API externe réessaie occasionnellement le même webhook, de sorte que n8n peut recevoir des événements en double en quelques secondes. Le flux crée des enregistrements de base de données et déclenche des actions en aval, donc vérifier simplement l’historique d’exécution ne semble pas fiable.
J’envisage d’utiliser PostgreSQL avec un ID d’événement unique et une requête INSERT … ON CONFLICT DO NOTHING avant de continuer le flux.
INSERT INTO webhook_events (event_id, received_at)
VALUES ($1, NOW())
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;
est-ce un bon modèle, ou gérerais-tu la déduplication différemment ?

Décrivez le problème/l’erreur/la question

Quel est le message d’erreur (le cas échéant) ?

Veuillez partager votre flux

(Sélectionnez les nœuds sur votre canevas et utilisez les raccourcis clavier CMD+C/CTRL+C et CMD+V/CTRL+V pour copier et coller le flux.)

Partagez la sortie renvoyée par le dernier nœud

Informations sur votre configuration n8n

  • Version n8n :
  • Base de données (par défaut : SQLite) :
  • Paramètre n8n EXECUTIONS_PROCESS (par défaut : own, main) :
  • Exécution de n8n via (Docker, npm, cloud n8n, application de bureau) :
  • Système d’exploitation :

Bonjour @Sophia_Bennett Oui, j’utiliserais la base de données comme source de vérité plutôt que de compter sur l’historique d’exécution de n8n.

Un event_id unique plus INSERT … ON CONFLICT DO NOTHING vous donne une vérification de déduplication atomique, même lorsque plusieurs workers reçoivent le même événement presque simultanément.

INSERT INTO webhook_events (event_id, received_at)
VALUES ($1, NOW())
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;

Si aucune ligne n’est retournée, traitez-la comme un doublon et arrêtez le flux de travail. J’ajouterais également un index/contrainte unique sur event_id et conserverais le dossier de déduplication aussi longtemps que le fournisseur peut renvoyer les événements.

Pour les flux de travail plus critiques, je suivrais également l’état (traitement, terminé, échoué) pour que les tentatives puissent reprendre en toute sécurité sans créer d’effets secondaires dupliqués.

Salut @Sophia_Bennett Bienvenue dans la Communauté !

Oui, c’est le bon modèle. ON CONFLICT DO NOTHING est atomique, donc deux livraisons au même moment ne peuvent pas toutes les deux passer. Pour le rendre sûr en production :

  1. Gate : ajoutez un nœud If après l’insertion : {{ $json.event_id }} n’est pas vide. Les doublons ne retournent aucune ligne et s’arrêtent là.
  2. Défaillances : si le workflow échoue après l’insertion, la nouvelle tentative est rejetée comme doublon et l’événement est perdu. Ajoutez une colonne status, définissez-la sur done à la fin, et supprimez ou marquez la ligne failed en cas d’erreur pour que la nouvelle tentative puisse passer.
  3. Répondez immédiatement dans le nœud Webhook. Les réponses lentes sont ce qui déclenche la plupart des nouvelles tentatives.
  4. Nettoyez les lignes plus anciennes que quelques jours avec un workflow Schedule.

Salut @Sophia_Bennett, oui, avec un event_id unique non-nul c’est une bonne barrière atomique. Mais ne supprime pas la ligne d’événement à chaque échec : une API en aval peut terminer l’action avant que n8n ne dépasse le délai d’expiration, donc relancer pourrait la répéter.

Si tes enregistrements métier sont dans la même base de données PostgreSQL, valide l’ID d’événement, les écritures métier et une ligne d’outbox pour le travail en aval ensemble. Une procédure stockée appelée via Postgres Execute Query peut faire cela, en appliquant les écritures uniquement quand l’insertion d’événement réussit. Retourne 200 via Respond to Webhook après ce commit.

Traite l’outbox séparément, en réutilisant une clé d’idempotence stable par événement/action si l’API réceptrice la supporte. Sinon, vérifie les résultats incertains avant de relancer. Conserve les ID d’événements selon la fenêtre de retry/replay du fournisseur, pas un délai arbitraire de quelques jours.

Un détail spécifique à n8n sur le chemin des doublons, puisque à la fois la porte If et « Respond to Webhook after commit » en dépendent :

Quand ON CONFLICT DO NOTHING … RETURNING détecte un doublon, le nœud Postgres ne produit aucun élément, et par défaut n8n n’exécute simplement rien après un nœud sans sortie. Donc le nœud If ne voit jamais le doublon, et si Respond to Webhook se trouve sur cette branche, la livraison en doublon risque de ne jamais recevoir un 200 correct. Avec un fournisseur qui réessaie sur du non-2xx ou un timeout, cela peut transformer un doublon en plusieurs.

Activer « Always Output Data » dans les paramètres du nœud Postgres le corrige : le nœud passe alors un élément vide sur un doublon, la porte If sur event_id l’attrape, et tu peux envoyer la branche des doublons directement à son propre Respond to Webhook avec un 200 pour que le fournisseur cesse de réessayer.

Oui, c’est un motif solide et largement utilisé.

Approche recommandée

INSERT INTO webhook_events (event_id, received_at)
VALUES ($1, NOW())
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;

Ensuite dans n8n :

• Si la requête retourne une ligne → c’est la première fois que vous voyez l’événement → continuez le workflow.

• Si elle retourne rien → c’est un doublon → arrêtez le workflow (ou routez vers une branche « doublon »).

Salut @Sophia_Bennett Oui, c’est un modèle robuste pour la production. Je ferais de PostgreSQL la source de vérité pour la dédupplication plutôt que de compter sur l’historique d’exécution de n8n.

La partie importante est la contrainte unique :
CREATE UNIQUE INDEX ux_webhook_events_event_id
ON webhook_events (event_id);

Alors votre insertion devient atomique :

INSERT INTO webhook_events (event_id, received_at)
VALUES ($1, NOW())
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;

Si RETURNING te donne l’event_id, cette exécution a bien revendiqué l’événement et peut continuer.

Si cela retourne rien, une autre exécution a déjà traité ou revendiqué cet événement, donc le flux de travail devrait s’arrêter avant de créer des enregistrements ou de déclencher des actions en aval.

Webhook
↓
Insérer l’ID d’événement
↓
Ligne retournée ?
├─ Oui → Traiter l’événement
└─ Non → Doublon → Arrêter

Évite aussi de mettre la vérification de dédupplication et l’opération métier réelle dans des étapes « vérifier puis insérer » séparées. Deux exécutions de n8n peuvent passer cette vérification en même temps. Laisse la contrainte unique de PostgreSQL gérer la condition de concurrence.

Un point important de plus : la dédupplication ne rend pas les actions en aval automatiquement idempotentes. Si le flux de travail envoie un paiement, un email, une requête API, etc., j’utiliserais le même ID d’événement comme clé d’idempotence là où le système en aval le supporte.