ediverse Explore the platform

Spotlight PEPPOL BIS Billing 3.0 The EU e-invoicing mandate is here — France Sept 2026, Belgium Jan 2026, Germany 2025.

— 01

Foundations

— 02

Standards

— 03

Flagship messages

— 04

Tools

Idempotent Receiver for EDI flows

On this page

An EDIFACT INVOIC received twice must produce one accounting invoice. Deduplication relies on the regulatory fields of each standard — not an application UUID — and takes contractual partner retransmission windows into account.

Problem

All EDI protocols explicitly expect retransmission: AS2 sends an MDN, otherwise the partner retransmits; AS4 has NRR + Retry-After; OFTP2 has EERP + retry timer; SFTP has its own application-level retry logic. The AS2 golden rule (RFC 4130 §10) even mandates that on missing MDN, the sender MUST retransmit. Consequence: every EDI receiver must treat each delivered message as potentially duplicated. The question is not "will this happen?" but "what happens when it does?". Not deduplicating = duplicate invoices, duplicate orders, lost stock, negative audit.

Forces

  • Explicit standards: each EDI standard has a contractual uniqueness field (UNB 0020, ISA13, BT-1 in UBL, AS4 MessageId).
  • Dedup window: must be at least as long as the maximum partner retry window (typically 7 days for AS2, 30 days for PEPPOL).
  • Reference reuse: EDIFACT and X12 standards allow reusing UNB/ISA13 numbers after a period — dedup must be bounded by (sender, ref, date), not just ref.
  • Auditability: each dedup trace must be stored to prove to the partner why their retry did not produce an invoice.
  • Performance: one lookup per ingested message — must be O(log n) with proper DB index.

Solution

Identify the standard-specific uniqueness key. Persist this key before any business side effect (creating an order, invoice, internal ASN). If the key already exists in the dedup window, return a positive functional ACK (the partner thinks it arrived, which is true — it arrived on first transmission) without replaying the side effect. Keep the dedup history for audit.

Structure

Uniqueness keys per standard:

EDIFACT  → UNB segment, element 0020 (Interchange Control Reference)
           composed with 0004 (Sender), 0010 (Recipient) and S004 (date/time)
           e.g. "UNB+UNOC:3+SENDER:ZZ+RECVR:ZZ+260518:1023+REF12345'"

X12      → ISA segment, element 13 (Interchange Control Number)
           composed with ISA06 (Sender ID) and ISA08 (Receiver ID)
           e.g. "ISA*00*          *00*          *01*SENDER         *01*RECVR
                *260518*1023*U*00401*000012345*0*P*>~"

UBL      → cbc:UUID (BT-46) or cbc:ID (BT-1) per profile
           e.g. <cbc:UUID>3aa5d6dc-65d3-4f01-8a47-2e7c12a44b8b</cbc:UUID>

AS4      → eb:MessageId from WS-Addressing header
           e.g. <eb:MessageId>uuid-12345@sender.example.com</eb:MessageId>

PEPPOL   → SBDH/DocumentIdentification/InstanceIdentifier
           e.g. <InstanceIdentifier>peppol-1234-5678</InstanceIdentifier>

EDI implementation

Multi-standard dedup table schema, indexed by (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),                -- drift detection
  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 before 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;

-- If is_new = true → process (create order, etc.)
-- If is_new = false → positive functional ACK without side effect
--   AND verify payload_sha256 vs new hash: if different, alert
--   (partner reuses ref with changed content = serious bug)

Purge window: for typical AS2/AS4 B2B, retaining 90 days is plenty. For PEPPOL, where the SBDH InstanceIdentifier must be unique forever, archive after 1 year into a frozen Elasticsearch index and keep the trace for 10-year fiscal audit.

Anti-patterns

  • Using an application UUID at ingestion instead of the contractual reference — loses partner semantics, retry creates duplicates.
  • Deduplicating only on interchange_ref without partner_id — Walmart and Carrefour may reuse the same ref.
  • Not verifying payload hash — a partner re-submitting REF12345 with new content is silently ignored (silent corruption).
  • Dedup window too short — a J+8 partner retry creates a duplicate because dedup has already purged J+7.
  • Confusing business idempotency and broker idempotency (see Transactional Inbox) — both are needed, complementary.

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) — Annex C on UNB 0020 Interchange Control Reference semantics. iso.org
  • X12 Foundations — TR3 Interchange Control Number Usage Guide. x12.org
  • OpenPEPPOL — SBDH Implementation Guide, section 5.2 (InstanceIdentifier uniqueness rules).