External item changed
Fires when a customer or vendor external item description is created or updated in D365FO. An external item description is a CustVendExternalItem record that maps your item number to the item number and text a specific customer or vendor (or a customer or vendor external item group) uses. Downstream systems can use this event to keep EDI mapping tables, B2B portals, and trading-partner catalogs in step with the cross-references maintained in D365FO. The same event is also emitted in bulk by the external item resync described below.
| Event class | MGCLatticeExternalItemChangedBusinessEvent |
| Contract class | MGCLatticeExternalItemChangedBusinessEventContract |
| Category | Inventory management |
| Module | ModuleAxapta::Inventory |
| Label IDs | ExternalItemChangedBusinessEventName / ExternalItemChangedBusinessEventDescription |
| Trigger | CustVendExternalItem Inserted and Updated data events (every insert and every update), plus the on-demand external item resync |
Trigger / Hook
- Handler class:
MGCLatticeExternalItemChangedEventHandlers, with two static data event handlers:[DataEventHandler(tableStr(CustVendExternalItem), DataEventType::Inserted)]onpublic static void CustVendExternalItem_onInserted(Common _sender, DataEventArgs _e), which sends the event withisUpdate = false.[DataEventHandler(tableStr(CustVendExternalItem), DataEventType::Updated)]onpublic static void CustVendExternalItem_onUpdated(Common _sender, DataEventArgs _e), which sends the event withisUpdate = true.
- Firing condition: every insert and every update of a
CustVendExternalItemrecord, whatever the source (the external item descriptions page, a data entity or import, or code), and for every module type (customer, vendor, customer group, vendor group). There's no field-change filter, so any update sends an event, even one that changes nothing a subscriber cares about. Deleting a cross-reference doesn't send an event. - Transaction: the handlers run inside the insert or update. When that write is part of a larger transaction, the event is written to the business events commit log inside it and is delivered only if the transaction commits.
- Enablement gate: both handlers check
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeExternalItemChangedBusinessEvent))before building the event. - Bulk resync (second send site): the action menu item
MGCLatticeProcessAllExternalItems("Publish all external item business events (resync)") asks for confirmation and then runsMGCLatticeBulkEventPublisherfor theExternalItementity type in a sandbox session (it isn't a batch job).MGCLatticeBulkEventSourceExternalItem.publishAll()sends the event for everyCustVendExternalItemrecord in the current legal entity, always withisUpdate = false, and the publisher pauses for 250 ms after every 500 events. Before reading any record, the resync checks that the event is published in the current legal entity (business events framework on, event activated). If not, it stops with "The business event MGCLatticeExternalItemChangedBusinessEvent is not activated in legal entity <company>, so no events were published. Activate it in the business events catalog and run the job again." The finish message counts only events actually published. The menu item is at System administration > Periodic tasks > Lattice business events > Publish all external item business events (resync), and theMGCLatticeProcessAllExternalItemsExecuteprivilege grants it to the integration administrator role through the batch maintenance duty (MGCLatticeBatchMaintenanceDuty).
[DataEventHandler(tableStr(CustVendExternalItem), DataEventType::Updated)]
public static void CustVendExternalItem_onUpdated(Common _sender, DataEventArgs _e)
{
CustVendExternalItem custVendExternalItem = _sender as CustVendExternalItem;
if (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeExternalItemChangedBusinessEvent)))
{
MGCLatticeExternalItemChangedBusinessEvent::newFromCustVendExternalItem(custVendExternalItem, true).send();
}
}
The resync loop in MGCLatticeBulkEventSourceExternalItem.publishAll():
while select custVendExternalItem
{
MGCLatticeExternalItemChangedBusinessEvent businessEvent = MGCLatticeExternalItemChangedBusinessEvent::newFromCustVendExternalItem(custVendExternalItem, false);
businessEvent.send();
_publisher.onPublished(businessEvent, custVendExternalItem.ExternalItemId);
}
Payload Contract
One row per [DataMember], in declaration order. All sources are fields on the triggering CustVendExternalItem record, mapped in initialize().
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | CustVendExternalItem.dataAreaId | Company of the cross-reference |
isUpdate | Is Update | boolean | Set by the sender | false from the Inserted handler and from the resync; true from the Updated handler. Serialized as true/false |
itemId | Item Number | ItemId | CustVendExternalItem.ItemId | Your internal item number |
externalItemId | External Item Number | ExternalItemId | CustVendExternalItem.ExternalItemId | The customer's or vendor's item number |
externalItemText | External Item Text | ExternalItemFreeTxt | CustVendExternalItem.ExternalItemTxt | The customer's or vendor's item text |
description | Description | Description | CustVendExternalItem.Description | |
accountRelation | Account Number | CustVendRel | CustVendExternalItem.CustVendRelation | Customer or vendor account, or the external item group for the group module types |
moduleType | Module Type | int | enum2int(CustVendExternalItem.ModuleType) | ModuleInventPurchSalesVendCustGroup value: 3 = Vend, 4 = Cust, 5 = VendGroup, 6 = CustGroup |
moduleTypeName | Module Type Name | str | enum2Symbol(enumNum(ModuleInventPurchSalesVendCustGroup), ...) | Symbol of the module type, for example Cust |
inventDimId | Inventory Dimension ID | InventDimId | CustVendExternalItem.InventDimId | Inventory dimension combination the cross-reference applies to |
abcCategory | ABC Category | int | enum2int(CustVendExternalItem.ABCCategory) | ABC enum as an integer: 0 = None, 1 = A, 2 = B, 3 = C |
abcCategoryName | ABC Category Name | str | enum2Symbol(enumNum(ABC), abcCategory) | Enum symbol: None, A, B or C |
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 raised inside the business transaction rather than when it commits; it orders the events of one business record, gaps are permanent and normal, and it must not be used to re-sequence the whole stream), correlationId (String, always Scope:Key - groups emissions that belong to one logical run; Event:<GUID> unique to this emission when the event 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.
Example Payload
{
"BusinessEventId": "MGCLatticeExternalItemChangedBusinessEvent",
"ControlNumber": 5637146214,
"EventId": "3C9E5B7A-1F24-4D8B-9A63-7E0C2D4F8B15",
"EventTime": "\/Date(1790244131000)\/",
"EventTimeIso8601": "2026-09-24T10:02:11.2191931Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{83564AF5-3123-8327-8F6E-9E02A4377FFF}",
"ParentContextRecordSubjects": [],
"eventSequence": 48213,
"correlationId": "Event:626670ca-8973-4d17-886d-cd83ed9dbac7",
"schemaVersion": 1,
"legalEntity": "usmf",
"isUpdate": true,
"itemId": "D0001",
"externalItemId": "CX-44871",
"externalItemText": "Mid-range speaker, black",
"description": "Contoso Retail catalog number",
"accountRelation": "US-003",
"moduleType": 4,
"moduleTypeName": "Cust",
"inventDimId": "AllBlank",
"abcCategory": 0,
"abcCategoryName": "None"
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("External item changed") or by filtering the Category column to Inventory management. 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 for the insert and update events. To seed a new subscriber with every existing cross-reference, activate the event first and then have an integration administrator run the resync menu item MGCLatticeProcessAllExternalItems in each legal entity; it sends one event per record, marked isUpdate = false.