Event-driven ERP integration lets orders, receipts, invoices, inventory changes, and master-data events reach other systems without making every business transaction wait for remote services. The risk is partial success. An ERP commit can succeed while event publication fails, or a broker can redeliver after a consumer updates its database but before acknowledgment. In financially meaningful flows, a duplicate is not harmless transport noise; it can become a second posting, shipment, or payment.
A reliable design combines several controls. The transactional outbox binds business state and publication intent in one local commit. A relay publishes at least once. Stable event identity lets consumers detect repeats. Idempotent business operations prevent duplicate outcomes. Ordering is limited to the entity that needs it. Replay uses immutable scope and approvals. Independent reconciliation finds lost, stuck, or semantically rejected work that technical delivery metrics cannot prove.
Define the business event contract
Publish a fact that has happened, such as sales order released, not an imperative with ambiguous ownership. Define source, event ID, type, occurred time, subject or aggregate ID, schema version, producer revision, correlation and causation IDs, and business payload. CloudEvents standardizes context attributes and states that the combination of source and id identifies a distinct event; a resent duplicate may retain that identity. Use those semantics consistently across relays and adapters.
Keep sensitive data out of routing metadata because intermediaries and logs can expose context attributes. Include only payload needed by subscribers or provide a stable reference where consumers are authorized to retrieve more. Version meaning, not only shape. Changing an amount from gross to net under the same field name is breaking even if JSON validation passes. Document delete, reversal, correction, and late-arriving semantics.
| Identifier | Scope | Purpose | Misuse to avoid |
|---|---|---|---|
| Event ID | One published fact | Duplicate detection and audit | Generating a new ID on relay retry |
| Aggregate ID | Order, invoice, item, or account | Per-entity ordering and state lookup | Treating it as globally ordered stream |
| Correlation ID | Business journey | Trace related work across systems | Using it as an idempotency key for every step |
| Causation ID | Triggering command or event | Explain event lineage | Replacing immutable source evidence |
| Consumer operation key | One downstream side effect | Idempotent result reuse | Deduplicating unrelated actions from the same event |
Commit business state and outbox atomically

Insert the outbox record in the same database transaction and transactional scope as the ERP change. Microsoft describes the pattern as saving the business object and event together so both commit or roll back. If the ERP exposes only released business APIs and no shared transaction, use a vendor event framework, change feed, or supported extension point that provides equivalent post-commit evidence. Do not dual-write to a broker inside the business transaction and assume both systems will agree.
The outbox stores immutable event identity, aggregate, sequence or version, payload or payload reference, schema, creation time, partition key, and publishing status. Protect it from casual update. The transaction should not mark an event published; the relay owns delivery state. Retain enough history to investigate and replay, but archive according to financial, privacy, and storage requirements. Outbox cleanup must never overtake the slowest recoverable subscriber obligation.
Make the relay retry-safe and observable
The relay polls or consumes a change log, publishes, then records progress. A crash after publish but before progress can send the same event again. That is expected in an at-least-once design, so the event ID remains unchanged. Use leases or partition ownership to scale relays and preserve required per-aggregate order. Measure oldest unpublished age, backlog, publish attempts, broker acknowledgment, and outbox-to-broker latency.
A published marker is operational evidence, not proof that every subscriber applied the event. Broker retention and consumer lag need separate monitoring. On an unknown publish outcome, query broker-supported state only if it is authoritative; otherwise repeat with the same identity. Never create a replacement event merely to get past a stuck relay because that defeats deduplication and obscures whether two business facts exist.
Implement consumer idempotency around the outcome
A consumer should commit its inbox or processed-event record and business update atomically in its own datastore. On repeat delivery, it returns the recorded outcome without repeating the side effect. A uniqueness constraint on source and event ID closes concurrent races. For an event that drives several independent actions, use operation-specific keys so retrying notification does not repeat a ledger posting and one completed action does not suppress another.
Deduplication windows in brokers can reduce traffic but are not a complete business control; replay may occur after the window or through another channel. Build domain idempotency as well. Creating an invoice may be keyed by source order and invoice purpose, while applying a versioned inventory state can reject stale versions. External APIs need propagated idempotency keys or a durable adapter ledger before the irreversible call.
| Failure point | Possible duplicate | Required control | Reconciliation evidence |
|---|---|---|---|
| After ERP commit, before relay | No duplicate yet; event is pending | Atomic outbox and backlog alert | Committed transaction has one outbox fact |
| After broker accept, before relay checkpoint | Same event republished | Stable event ID and consumer inbox | Broker deliveries map to one event |
| After consumer update, before acknowledgment | Consumer sees event again | Atomic inbox plus business update | One downstream business outcome |
| After external side effect, before local record | Partner action may repeat | Partner idempotency or pre-call operation ledger | Partner reference tied to operation key |
| During operator replay | Old facts re-enter active flow | Immutable scope, dry run, approvals, and rate limit | Expected population equals applied or skipped outcomes |
Limit ordering to the business entity
Global ordering sacrifices throughput and usually exceeds business need. Partition by stable aggregate when create, update, and cancel for one order must remain ordered. Include aggregate version so consumers can detect gaps, duplicates, and stale events. Cross-aggregate processes, such as order and payment, need an orchestrated workflow, explicit state machine, or reconciliation rather than assumptions about broker arrival order.
When a gap appears, pause that aggregate, retrieve missing state, or use a documented overwrite rule. Do not block every partition. Events can also arrive after a correction or snapshot, so define whether consumers apply deltas, replace state by version, or query the source. The choice determines replay safety. Commutative updates are easier to retry than increment commands that can apply twice.
Operate replay as a controlled migration
Specify source event IDs, time and aggregate filters, target consumers, reason, expected count, schema compatibility, rate, start and stop criteria, and approver. Produce a dry-run manifest and check current downstream state. Replay into an isolated topic or consumer mode when old events need transformation. Keep original identity for the same fact; if creating a new corrective fact, give it a new ID and explicit link to what it corrects.
Throttle against ERP, broker, consumer, and partner capacity. Observe applied, duplicate-skipped, rejected, dead-lettered, and pending counts. A technically successful replay can still produce wrong outcomes if historical schemas or business rules are interpreted using current logic. Pin the required handler version or transform deliberately, and record that decision. Stop when unexpected side effects or reconciliation drift appears.
Model reversal and compensation explicitly
A later failure may require a compensating business transaction rather than deleting prior events. Microsoft notes that compensation is application-specific and may not restore the exact original state because concurrent work can occur. For ERP flows, issue credit, cancel reservation, reverse posting, or open manual review through supported operations. Emit new facts that preserve history instead of editing the old event.
Define which actions are automatically compensatable, which need approval, and which are irreversible. Compensation handlers must also be idempotent. Track the original transaction, failed step, compensation state, financial reference, and human decision. A saga reported complete only after every forward step or approved compensation reaches a terminal, reconciled state.
Reconcile business populations end to end
Compare ERP committed facts, outbox population, broker publication, consumer acceptance, and final business records by event type, entity, period, company, and value. Technical counters reveal backlog, while business controls reveal missing invoices or mismatched amounts. Reconciliation should classify pending within service level, duplicate skipped, rejected, compensated, and unexplained. Only the last category represents unresolved integrity risk.
Give operations a transaction view keyed by business reference and event identity. It should show state without allowing arbitrary republish. Restrict replay and correction actions, require reason and approval, and preserve audit logs. Alert on age and materiality, not only raw queue depth; one stuck high-value settlement can matter more than thousands of low-risk telemetry events.
Key takeaways
- Commit the business change and outbox intent in one supported local transaction.
- Keep event identity stable across publication retries.
- Make each downstream business operation idempotent with durable evidence.
- Order by aggregate only where the domain requires it.
- Replay immutable, approved populations under capacity controls.
- Reconcile from ERP commit to final business outcome, including compensation.
Frequently asked questions
Can a broker guarantee no duplicate ERP transaction?
Not by itself. The ERP or external side effect may be outside the broker transaction. Use stable identity, consumer atomicity, domain idempotency, and reconciliation at the business boundary.
Should replay assign new event IDs?
Retain identity when redelivering the same fact. Assign a new ID only for a genuinely new correction or compensating fact, and link it to the original.
Can old outbox rows be deleted after publication?
Only after retention, replay, audit, and consumer recovery obligations are satisfied and archived evidence remains usable. Publication alone does not prove downstream application.
Conclusion
Event-driven ERP integration is dependable when duplication and delay are designed outcomes rather than surprises. Atomic outbox creation closes the commit-to-publish gap; stable identity and idempotent consumers bound redelivery; scoped ordering, governed replay, compensation, and business reconciliation protect material transactions. Together these controls let asynchronous flows scale without trading away financial and operational integrity.