ediverse Explorer la plateforme

À la une PEPPOL BIS Billing 3.0 L’obligation européenne d’e-invoicing arrive : France sept 2026, Belgique janv 2026, Allemagne 2025.

Idempotent Receiver pour flux EDI

Un INVOIC EDIFACT reçu deux fois doit générer une seule facture comptable. La déduplication s'appuie sur les champs réglementaires de chaque standard — pas sur un UUID applicatif — et tient compte des fenêtres de retransmission contractuelles partenaire.

Problème

Tous les protocoles EDI prévoient explicitement la retransmission : AS2 envoie un MDN, sinon le partenaire retransmet ; AS4 a NRR + Retry-After ; OFTP2 a EERP + retry timer ; le SFTP a sa propre logique de retry au niveau applicatif. La règle d'or AS2 (RFC 4130 §10) impose même qu'en cas d'absence de MDN, le sender DOIT retransmettre. Conséquence : tout receveur EDI doit traiter chaque message livré comme potentiellement dupliqué. Le problème n'est pas « est-ce que ça va arriver ? » mais « qu'est-ce qui se passe quand ça arrive ? ». Ne pas dédupliquer = factures en double, commandes en double, stock perdu, audit négatif.

Forces

  • Standards explicites : chaque standard EDI a un champ contractuel d'unicité (UNB 0020, ISA13, BT-1 en UBL, MessageId AS4).
  • Fenêtre de déduplication : doit être au moins aussi longue que la fenêtre maximale de retry du partenaire (typiquement 7 jours pour AS2, 30 jours pour PEPPOL).
  • Réutilisation des références : les standards EDIFACT et X12 permettent de réutiliser les numéros UNB/ISA13 après une période — la déduplication doit être bornée par (sender, ref, date), pas juste ref.
  • Auditabilité : la trace de chaque dédup doit être stockée pour pouvoir prouver au partenaire pourquoi son retry n'a pas généré de facture.
  • Performance : lookup unique par message ingéré — doit être O(log n) avec index DB approprié.

Solution

Identifier la clé d'unicité spécifique à chaque standard. Persister cette clé avant tout side effect métier (création de commande, facture, ASN interne). Si la clé existe déjà dans la fenêtre de dédup, retourner un ACK fonctionnel positif (le partenaire pense que c'est bien arrivé, ce qui est correct — c'est arrivé à la première transmission) sans rejouer le side effect. Conserver l'historique de dédup pour audit.

Structure

Clés d'unicité par standard:

EDIFACT  → UNB segment, élément 0020 (Interchange Control Reference)
           composé avec 0004 (Sender) et 0010 (Recipient) et S004 (date/time)
           Ex: "UNB+UNOC:3+SENDER:ZZ+RECVR:ZZ+260518:1023+REF12345'"

X12      → ISA segment, élément 13 (Interchange Control Number)
           composé avec ISA06 (Sender ID) et ISA08 (Receiver ID)
           Ex: "ISA*00*          *00*          *01*SENDER         *01*RECVR
                *260518*1023*U*00401*000012345*0*P*>~"

UBL      → cbc:UUID (BT-46) ou cbc:ID (BT-1) selon profil
           Ex: <cbc:UUID>3aa5d6dc-65d3-4f01-8a47-2e7c12a44b8b</cbc:UUID>

AS4      → eb:MessageId du header WS-Addressing
           Ex: <eb:MessageId>uuid-12345@sender.example.com</eb:MessageId>

PEPPOL   → SBDH/DocumentIdentification/InstanceIdentifier
           Ex: <InstanceIdentifier>peppol-1234-5678</InstanceIdentifier>

Implémentation EDI

Schéma de table dédup multi-standard, indexé par (partner_id, standard, interchange_ref, date) :

CREATE TABLE edi_dedup (
  partner_id        VARCHAR(40) NOT NULL,
  standard          VARCHAR(20) NOT NULL,    -- 'EDIFACT' | 'X12' | 'UBL' | 'AS4'
  interchange_ref   VARCHAR(80) NOT NULL,    -- UNB 0020, ISA13, etc.
  message_type      VARCHAR(20),             -- 'INVOIC', '810', 'Invoice'
  payload_sha256    CHAR(64),                -- détection drift
  received_at       TIMESTAMPTZ DEFAULT now(),
  first_seen_at     TIMESTAMPTZ NOT NULL,
  retry_count       INT DEFAULT 0,
  PRIMARY KEY (partner_id, standard, interchange_ref, message_type)
);

-- Lookup avant ingestion
WITH new_msg AS (
  INSERT INTO edi_dedup (partner_id, standard, interchange_ref, message_type, payload_sha256, first_seen_at)
  VALUES ('WALMART', 'EDIFACT', 'REF12345', 'INVOIC', $1, now())
  ON CONFLICT (partner_id, standard, interchange_ref, message_type)
  DO UPDATE SET retry_count = edi_dedup.retry_count + 1
  RETURNING (xmax = 0) AS is_new, payload_sha256
)
SELECT is_new, payload_sha256 FROM new_msg;

-- Si is_new = true → traiter (créer commande, etc.)
-- Si is_new = false → ACK fonctionnel positif sans side effect
--   ET vérifier payload_sha256 vs nouveau hash : si différent, alerter
--   (partenaire réutilise le ref avec contenu changé = bug grave)

Fenêtre de purge : pour AS2/AS4 typique B2B, conserver 90 jours est largement suffisant ; pour PEPPOL où le SBDH InstanceIdentifier doit être unique éternellement, archiver après 1 an dans un index Elasticsearch frozen et garder la trace pour audit fiscal 10 ans.

Anti-patterns

  • Utiliser un UUID applicatif à l'ingestion plutôt que la référence contractuelle — perd la sémantique partenaire, le retry crée des doublons.
  • Dédupliquer uniquement sur interchange_ref sans partner_id — un Walmart et un Carrefour peuvent réutiliser le même ref.
  • Ne pas vérifier le hash du payload — un partenaire qui re-soumet REF12345 avec un nouveau contenu est ignoré silencieusement (corruption silencieuse).
  • Fenêtre de dédup trop courte — un retry partenaire J+8 crée un doublon parce que la dédup a déjà purgé J+7.
  • Confondre idempotence métier et idempotence broker (cf Transactional Inbox) — les deux sont nécessaires, complémentaires.

Patterns liés

Sources

  • Hohpe G., Woolf B. — Enterprise Integration Patterns: Idempotent Receiver, Addison-Wesley 2003, §528. enterpriseintegrationpatterns.com
  • RFC 4130 — MIME-Based Secure Peer-to-Peer Business Data Interchange Using HTTP, Applicability Statement 2 (AS2), §10 (Retransmission). datatracker.ietf.org/doc/rfc4130
  • UN/EDIFACT Syntax Rules Version 4 (ISO 9735) — Annexe C sur la sémantique du UNB 0020 Interchange Control Reference. iso.org
  • X12 Foundations — TR3 Interchange Control Number Usage Guide. x12.org
  • OpenPEPPOL — SBDH Implementation Guide, section 5.2 (InstanceIdentifier uniqueness rules).