Back to Blog
Published:
Last Updated:
Fresh Content
ERP Write-Back & IntegrationChapter 4

Business events give you no ordering guarantee. What do you build instead?

8 min read
1,861 words
high priority
Muhammad Mudassir

Muhammad Mudassir

Founder & CEO, Cognilium AI

TL;DR

Microsoft states it under a heading called Limitations - the order in which Finance and Operations emits business events isn't guaranteed to preserve the order in which they're delivered. The control number is a dedupe key, not a sequence. So you build consumers that are idempotent, derive state from the record rather than the event sequence, and reconcile against the ERP rather than against the event stream.

Business events give you no ordering guarantee. What do you build instead?

Microsoft says so twice on one page, and the second time it is under a heading that reads Limitations:

"Business events that occur in finance and operations apps are processed asynchronously across multiple systems to deliver them to the target endpoint. Therefore, the order in which the apps emit the events isn't guaranteed to preserve the order in which they're delivered to the endpoints." — Business events overview

"No ordering guarantee" is our compression of Microsoft's "isn't guaranteed to preserve". We went looking for a narrower form and did not find one: across the overview, the developer documentation, the endpoints page, the troubleshooting page and the Azure Service Bus Topic how-to — the endpoint type where a first-in-first-out option would sit if one existed — the statement is made about the framework, and no page scopes it to an adapter or a broker. A near-identical sentence is the second of the three limitations listed on the data events page.

So the design question is not how to get the order back. It is what you build that never needed it.

What Microsoft commits to, and what it declines to

Business events [GA] — the troubleshooting page dates the status plainly: "This change was made when the business events feature was made generally available in Platform update 26." The overview calls them "a mechanism that lets external systems receive notifications from finance and operations apps", then puts a hard boundary on the pattern in an Important callout:

"Don't consider business events as a mechanism for exporting data. By definition, business events are supposed to be lightweight and nimble. They aren't intended to carry large payloads to fulfill data export scenarios."

The developer documentation says it harder — "the use of business events for data transfer scenarios is a misuse of the business events framework" — and carries the one guarantee that everything below rests on:

"The business event sending links to the commit of the underlying transaction. If the underlying transaction is aborted, the business event isn't sent."

Read that as a contract. An event tells you a transaction committed. It does not tell you what the record looks like now, because the payload was assembled at that commit — the same page instructs developers to "send the business event at the point where the payload information is available." That distinction is what the four design rules further down are built on.

The control number is a dedupe key. It is not a sequence.

Microsoft's Idempotency section, verbatim and complete:

"Business events enable idempotent behavior on the consuming side by having a control number in the payload. The consuming application can use the unique control number to detect duplicate delivery. The consuming application can't misread the control number as the sequence number because the control number isn't sequential. There can be gaps in the numbering space. The order in which events emitted in finance and operations apps isn't guaranteed to preserve the order in which they're delivered to the endpoints."

Two facts, and the second is the one teams get wrong. The control number is unique, so it identifies a delivery. It "isn't sequential" and "there can be gaps in the numbering space", so what it identifies is a delivery and not a position in a sequence.

Our position, not Microsoft's: a gap is therefore not evidence of a missing event, and a monitor built on sequence continuity will page someone at three in the morning for a numbering space behaving exactly as documented. We would not build gap detection on the control number at all. Microsoft's page states what the field is for and warns against the misreading; the alerting consequence is our inference from those two sentences.

Three constraints that shape the consumer before you write it

All from the overview page, under Business events parameters — Microsoft's numbers, Microsoft's field names.

Processing threads — What it controls: how many dedicated batch threads process business events. Microsoft's ceiling: "The maximum value is 4." Scope of that ceiling: "With dedicated processing for business events, the thread count is the number per Batch Application Object Server (AOS) instance."

Bundle size — What it controls: "The number of events to group together at a time for processing by a thread." Microsoft's trade-off runs both ways: a larger number "reduce[s] the ability to distribute the events to parallel threads", and "if you make the number too small, you cause unnecessary parallelization on small bundles."

Retry count · Wait time between retries · Endpoints allowed per event · Key vault secret cache interval — Microsoft's defaults: 3 retries · 1,000 milliseconds between them · 10 endpoints per event per legal entity · 5 minutes of secret caching. The HTTPS adapter's default timeout is 10 seconds.

The thread ceiling is the one that decides architecture, and Microsoft's own caution is that "threads are a shared resource for all batch processing, take care when you change the thread allocation for business events." The ordering limitation is this same fact seen from the other end: parallel threads over bundled events is how emission order stops being delivery order.

The expired secret — and the word Microsoft does not use

Our own notes carried this as silent event loss. Microsoft's wording is narrower, and the difference matters:

"The business event endpoint configuration requires developers to create an application secret in Microsoft Entra ID. However, after completing the initial setup, developers often forget to update the endpoint when the secret expires. As a result, business events sent through that endpoint fail until the new secret is manually updated, which can lead to event loss." — Manage business event endpoints

Microsoft says events fail, and that the failure "can lead to event loss". It does not say the failure is silent, and on four of the business-events pages opened for this article — the overview, the endpoints page, the developer documentation and the troubleshooting page — the word does not appear.

The overview says the opposite about failures generally: "if all attempts are unsuccessful, the system saves the business event in an error log", and that log carries a Resend action and a Download payload option.

Our reading: the loss is recorded and recoverable, so any silence is organisational — an error log with no subscriber is still an error log. That is a monitoring gap rather than a framework gap, and it is fixable this week.

Microsoft's mitigation is one field and three banners. The endpoint configuration page carries a SecretExpiryDate field, and the classification is "Info – The secret expires within the next 30 days. Warning – The secret expires within the next 15 days. Error – The secret expires within one day or has already expired." Fill the field. It is the cheapest reliability work in this pattern.

What you build instead

Ours, not Microsoft's — four design positions, each anchored to a sentence quoted above.

Consumers idempotent on the control number — Anchored on: Microsoft's stated purpose for the field, "to detect duplicate delivery". In build terms: the consumer records processed control numbers and discards repeats, including repeats produced by a Resend from the error log. Retries and manual resends both arrive as duplicates, so this is not an edge case.

State derived from the record, not from the event sequence — Anchored on: "The business event sending links to the commit of the underlying transaction." In build terms: the event is a trigger to go and read the entity, not a description of it. Two events for the same purchase order arriving out of order converge on the same answer, because both re-read the same record.

Order restored on your own business key, downstream — Anchored on: the control number "isn't sequential", and delivery order isn't emission order. In build terms: if a process genuinely needs sequence, sequence it on a key you own — a document number plus a version, or a posted timestamp from the payload — inside your own service, never on the framework's numbering space.

Reconciliation against the ERP, not against the event stream — Anchored on: Microsoft's Important callout, "Don't consider business events as a mechanism for exporting data" — so the stream was never a complete record to reconcile against. In build terms: the periodic truth check re-reads Finance and Operations and compares it with your projection. Counting events proves nothing about state.

Together these are one rule with four faces: treat the event as a signal and the ERP as the state. A consumer built that way is indifferent to the three delivery behaviours quoted above — reordering, duplicate delivery, and a resend that arrives days later once somebody renews a secret. It is not indifferent to the thread ceiling: that sets how fast the signal reaches you, and no consumer design changes it.

Why this is the write path we want anyway

Dynamics keeps the record and the transaction; the Optimizer subscribes to what committed, re-reads the record, and writes its decision back through a governed surface. That is "we don't touch your ERP core — we surround it with intelligence" drawn in code. Subscribing to an out-of-box event is setup rather than code: Microsoft's overview activates events from the catalog and configures endpoints on a page, reserving extensions for developers implementing new business events. So the ordering limitation is not a defect to route around. It is the price of staying outside the transaction, and the price is right.

What to check this week

  1. Open System administration > Set up > Business events, then the Errors tab. Microsoft's page says it "shows all errors across all business events". If nothing is subscribed to that tab, your event loss is already silent — organisationally, not by design.
  2. Check each endpoint for a SecretExpiryDate value. An empty field means no banner, and no banner means the Error state arrives as a failed integration instead of a warning.
  3. Find every consumer that infers a missing event from a gap in the control number, and delete that logic. Microsoft documents the gaps.
  4. Read your Processing threads and Bundle size settings together, against the ceiling of four per Batch AOS instance — not one in isolation.

About Cognilium Cognilium builds AI optimization apps for Microsoft Dynamics 365 — companion apps that optimize the pricing, inventory, warehouse and planning decisions your ERP manages but can't optimize. Dynamics is your system of record. Cognilium is your system of intelligence. https://cognilium.ai · https://www.linkedin.com/company/37180269/

Worked example. Modelled from public documentation and typical operations.

The write path is the first thing an IT buyer audits and the last thing anyone budgets for. Book a fifteen-minute call and we will walk your event topology and write surfaces with you on your own environment — no deck. https://cognilium.ai

Sources

Sources

Share this article

Muhammad Mudassir

Muhammad Mudassir

Founder & CEO, Cognilium AI

Mudassir Marwat's argument is that ERP systems record decisions they never optimise.

Founder & CEO of Cognilium AI; 37 AI agents in production across four products; 4 production AI products built and operated; three clouds in production (AWSGCPAzure)
Agentic AIRAG → GraphRAG retrievalVoice AIMulti-Agent Orchestration
Next in this series
Why do your data events never fire for some entities?
Chapter 5 · 8 min
In short

Key takeaways

  • Microsoft states under a heading called Limitations that the order in which Finance and Operations emits business events isn't guaranteed to preserve the order in which they're delivered to endpoints. The statement is about the framework, not about one endpoint type.
  • The control number in the payload is a uniqueness key for detecting duplicate delivery. Microsoft states that it isn't sequential and that there can be gaps in the numbering space, so it identifies a delivery rather than a position in a sequence.
  • Business event sending is linked to the commit of the underlying transaction, and an aborted transaction sends no event. That makes an event a reliable signal that something committed and an unreliable description of current state.
  • Microsoft's own boundary is that business events are not a mechanism for exporting data, so the event stream is not a record you can reconcile against. Reconcile against the ERP.
  • A consumer that is idempotent, reads the record on every trigger, and sequences on its own business key is indifferent to reordering, duplicate delivery, retries and manual resends alike. It is still bound by the thread ceiling, which sets how fast the signal arrives.
What goes wrong

Common mistakes to avoid

  • Building gap detection or missing-event alerting on the control number. Microsoft documents the numbering space as non-sequential with gaps, so the alert fires on correct behaviour.
  • Treating the event payload as the current state of the record. It describes a commit that already happened; anything that happened after it is not in the payload.
  • Leaving the endpoint's secret expiry date blank. The field exists so the platform can warn you before the secret expires rather than after events start failing.
  • Reading business events as a data export path. Microsoft's developer documentation calls that a misuse of the framework and points those scenarios back to data management.