Skip to main content

Business event publish failed

Fires when Lattice fails to publish another business event and records the failure as an Error row in the Lattice business event log (MGCLatticeBusinessEventLog). It turns a publish failure that would otherwise have to be found by browsing the log into a push notification an integration team can subscribe to, and it carries the logRecId of the exact log row for drill-down and replay. Only events that opt into Lattice business event logging are monitored - today that's the Retail assortment published event, which is sent after its own transaction commits so that a publish failure can be caught - and only failures that happen outside a database transaction are reported.

Event classMGCLatticePublishFailedBusinessEvent
Contract classMGCLatticePublishFailedBusinessEventContract
CategorySystem administration
ModuleModuleAxapta::SystemAdministration
Label IDsDeliveryFailedBusinessEventName / DeliveryFailedBusinessEventDescription
TriggerBusinessEventsPublisher.publish() -> publish of an opt-in event throws outside a database transaction -> Error row written to MGCLatticeBusinessEventLog

Trigger / Hook​

  • Extension class: MGCLatticeBusinessEventsPublisher_Class_Extension, decorated with [ExtensionOf(classStr(BusinessEventsPublisher))].
  • CoC method signature: public void publish(BusinessEventsBase _businessEvent) - the method every BusinessEventsBase.send() calls.
  • What "publish" means here: the platform publish() builds the event's payload and writes it to the business events commit log; delivery to your endpoint happens later and asynchronously. This event reports failures in the publish step, for example an exception while building the contract or while writing the commit log. Failures delivering to the endpoint afterwards are handled by the platform's business events framework and aren't reported by this event. A payload the platform can't serialize isn't reported either: the platform records it in its own business events exception log without raising an error, so the publish looks successful to Lattice.
  • Opt-in scope: only events that implement IMGCLatticeBusinessEventLoggable are logged and monitored. In this release that's Retail assortment published (plus this event, which never alerts on itself). Every other business event is passed straight through: no log row, no alert, and a publish failure reaches the code that sent the event exactly as it would without Lattice.
  • Firing condition:
    1. The wrapper runs next publish(_businessEvent) in a try.
    2. If the publish fails outside a database transaction, the catch runs: the failure of an opt-in event is kept from the caller and recorded; any other event's failure is rethrown.
    3. If the publish fails inside a database transaction, X++ doesn't run the catch: the exception propagates and rolls back the caller's transaction, and nothing is logged or raised, because anything written would be rolled back with it.
    4. In finally, for an opt-in event, MGCLatticeBusinessEventLogHelper::logPublishOutcome() writes the log row: Error for a failure outside a transaction; Success for a publish that returned normally and actually wrote the event to the commit log (the framework is on and the event is activated). Both kinds of row store the serialized contract. For an Error row it is the contract the failed publish built; if building the contract was the failure, it is tried once more under a try/catch (this runs outside a transaction, so the catch works), and the row stores no payload if it fails again.
    5. For an Error row, raisePublishFailedEvent() sends this event, unless the event that failed is this event itself (that stops a failed alert from raising another alert; the failed alert still gets its own Error row). Error rows are only written outside a transaction, so its catch does contain a failure to raise the alert.
  • No try/catch protects the Success logging - inside a transaction it couldn't. Instead the path is guarded: it runs only for opt-in events, never for a failure inside a transaction, and for a success only when the contract was already built and cached during the publish, so it isn't built again.
  • Replays: the business event reconciliation sweep (MGCLatticeBusinessEventReconciliationSweep) re-publishes Error rows of events that support replay, from the contract stored on the row. The replay keeps the original payload, eventSequence and correlationId; it gets a new EventId. While it runs, the replayed event is covered by MGCLatticeBusinessEventReplayContext, so a failed replay is not logged as a new Error row and does not raise this event again: the failure goes back to the sweep, which records it on the original row (RetryCount, LastRetryDateTime, LastRetryError) and leaves the row in Error, to be retried on the next run until the maximum retries is reached (then the row is marked Exhausted). The row is marked Resolved only when the replay wrote the event to the commit log again. A successful replay also writes a new Success row. The log is shared by every legal entity and records the legal entity each event was raised in (LegalEntity); the sweep covers the whole environment in one run and replays each Error row in that legal entity. Schedule one sweep job for the environment.
  • Enablement gate: BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticePublishFailedBusinessEvent)).
public void publish(BusinessEventsBase _businessEvent)
{
boolean isLoggable = _businessEvent is IMGCLatticeBusinessEventLoggable;
boolean insideTransaction = appl.ttsLevel() > 0;
boolean isReplay = isLoggable && MGCLatticeBusinessEventReplayContext::isReplaying(_businessEvent);
int infologStart = infologLine() + 1;
boolean published = false;
str exceptionText = '';
System.Exception publishException;

try
{
next publish(_businessEvent);
published = true;
}
catch (publishException)
{
// X++ runs this only when publish was called outside a transaction; the transaction check
// keeps a failure inside one propagating even if the catch were ever reached there. A replay's
// failure goes back to the replayer, which records it on the row being replayed.
if (!isLoggable || insideTransaction || isReplay)
{
throw;
}

exceptionText = MGCLatticeBusinessEventLogHelper::innermostExceptionMessage(publishException);
}
finally
{
if (isLoggable && (published || (!insideTransaction && !isReplay)))
{
MGCLatticeBusinessEventLogHelper::logPublishOutcome(_businessEvent, published, infologStart, exceptionText);
}
}
}

Payload Contract​

One row per [DataMember], in declaration order. Sources are the Error row just written to MGCLatticeBusinessEventLog, mapped in initialize().

JSON fieldDisplay labelType / EDTSourceNotes
failedBusinessEventIdFailed Business Event IDBusinessEventsIdMGCLatticeBusinessEventLog.BusinessEventIdCatalog ID of the event that failed to publish (its class name)
identifierIdentifierstrMGCLatticeBusinessEventLog.IdentifierBusiness identifier reported by the failed event's getEventIdentifier() - the assortment ID for Retail assortment published
legalEntityLegal EntityDataAreaIdMGCLatticeBusinessEventLog.LegalEntityThe legal entity the failed event was raised in, lower case
failureDetailsFailure DetailsstrMGCLatticeBusinessEventLog.StatusDetailsOnly this failure: the error messages the failed publish wrote to the infolog, one per line; when it wrote none (typically a .NET exception), the exception's message. Earlier messages in the session and warnings or info messages aren't included
logRecIdLog Record IDint64MGCLatticeBusinessEventLog.RecIdUse it to find the row in the Business event log. The value is local to the environment that wrote it
failedDateTimeFailed Date TimeutcdatetimeDateTimeUtil::utcNow() when the contract is builtUTC, \/Date(ms)\/ form

Every Lattice event also carries the Lattice ordering and correlation envelope, inherited from MGCLatticeBusinessEventContractBase: eventSequence (Int64 - increasing across the whole catalog and all companies, allocated when the event is built; gaps are normal), correlationId (String, always Scope:Key - Event:<guid> when the emission belongs to no run) and schemaVersion (Int - the payload shape version; 1 for this contract). D365 F&O also adds its standard envelope fields to every business event payload: BusinessEventId, BusinessEventLegalEntity, ContextRecordSubject, ControlNumber, EventId, EventTime, EventTimeIso8601, InitiatingUserAADObjectId, MajorVersion, MinorVersion, and ParentContextRecordSubjects. Do not rely on the order of members in the delivered JSON.

This event doesn't call correlateTo, so correlationId is unique to each emission; use logRecId and identifier to tie the alert to the failed event.

Example Payload​

{
"BusinessEventId": "MGCLatticePublishFailedBusinessEvent",
"ControlNumber": 5637150988,
"EventId": "E81C3D57-2B94-4A06-9F7E-C45A1D8B3F62",
"EventTime": "\/Date(1790245328000)\/",
"EventTimeIso8601": "2026-09-24T10:22:08.4904018Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{CEA60214-F014-08F9-549B-2B2A66106F64}",
"ParentContextRecordSubjects": [],
"eventSequence": 48390,
"correlationId": "Event:3a9d5e72-b418-4c60-8e2f-7d1c0b6a94e3",
"schemaVersion": 1,
"failedBusinessEventId": "MGCLatticeRetailAssortmentPublishedBusinessEvent",
"identifier": "000012",
"legalEntity": "usmf",
"failureDetails": "The business event contract could not be built for assortment 000012.",
"logRecId": 5637149210,
"failedDateTime": "\/Date(1790245328000)\/"
}

Activation​

In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Business event publish failed") or by filtering the Category column to System administration. Select it and use Activate to bind it to a configured endpoint (for example, an Azure Service Bus queue, Event Grid topic, or HTTPS webhook).

No other setup is needed. To inspect or replay a failed event, open System administration > Inquiries > Lattice business events > Business event log and find the row by logRecId; the page also links to the business event reconciliation sweep, which re-publishes the Error rows of every legal entity, each in the legal entity it was raised in, and can be scheduled on a recurrence (one job for the environment). After a sweep, the row's Reconciliation state, Retry count, Last retry and Last replay error columns show what happened: Resolved means the event was published again; Not reconciled with a retry count and a last replay error means the replay failed and will be tried again; Exhausted means the sweep has stopped retrying it.

Because a failed replay doesn't raise this event again, one alert is sent per failed publish, not per replay attempt. To be told about rows the sweep has given up on, watch the business event errors count tile in the Integration operations workspace, which counts Error rows that are Not reconciled, Exhausted and Manual intervention required rows.