Vendor hold status changed
Fires when a vendor's hold status, the Vendor hold field (VendTable.Blocked), changes to a different value: a hold is applied (for example No to All), changed (for example Invoice to Payment), or released (back to No), and also when a vendor is created already on hold. The payload carries both the new and the previous status. Downstream systems can use it to mirror supplier status in procurement portals and P2P tools, stop or resume payment runs in treasury systems, or alert buyers and category managers.
| Event class | MGCLatticeVendorHoldStatusChangedBusinessEvent |
| Contract class | MGCLatticeVendorHoldStatusChangedBusinessEventContract |
| Category | Accounts payable |
| Module | ModuleAxapta::Vendor |
| Label IDs | VendorHoldStatusChangedBusinessEventName / VendorHoldStatusChangedBusinessEventDescription |
| Trigger | VendTable Updated data event -> Blocked differs from orig().Blocked; VendTable.insert() -> new vendor with Blocked other than No |
Trigger / Hook
- Handler class:
MGCLatticeVendorHoldStatusChangedEventHandlers.- Updates:
public static void VendTable_onUpdated(Common _sender, DataEventArgs _e), a table data-event handler decorated with[DataEventHandler(tableStr(VendTable), DataEventType::Updated)]. It fires when the updated vendor'sBlockedvalue differs from itsorig()value, in either direction, and reports theorig()value as the previous status. Saves that leave Vendor hold unchanged are ignored. - New vendors:
public static void raiseForNewVendor(VendTable _vendTable), called by theVendTable.insert()wrapperMGCLatticeVendTable_Table_Extensionafter the vendor is inserted. It fires when the new vendor'sBlockedis anything butNo, withNoas the previous status andisNewVendor=true, so a subscriber that mirrors hold status also learns about vendors that start on hold. A vendor created without a hold raises only Vendor created.
- Updates:
- Source-independent: both paths fire for vendors created or updated from any source (the Vendors page, data entities and integrations, or X++ logic) that goes through the table's insert and update.
- Ordering: for a vendor created on hold, Vendor created is sent first, then this event, both after the insert. For an update, the data event runs inside
VendTable.update(), before the Lattice Vendor updated event, which is sent afterupdate()returns; when both events are active, one save produces Vendor hold status changed first (lowereventSequence), then Vendor updated. - Timing: the event is sent inside the vendor's insert or update transaction.
send()writes it to the business events commit log in that transaction and it is delivered after commit, so a save that rolls back never delivers the event. - Inserts and updates that bypass the table methods (
doInsert(),doUpdate(), or set-based statements with data methods skipped) do not raise it. - Enablement gate: guarded by
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeVendorHoldStatusChangedBusinessEvent)), so the event only sends when activated in the business events catalog.
[DataEventHandler(tableStr(VendTable), DataEventType::Updated)]
public static void VendTable_onUpdated(Common _sender, DataEventArgs _e)
{
VendTable vendTable = _sender as VendTable;
CustVendorBlocked previousBlocked = vendTable.orig().Blocked;
if (vendTable.Blocked == previousBlocked)
{
return;
}
if (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeVendorHoldStatusChangedBusinessEvent)))
{
MGCLatticeVendorHoldStatusChangedBusinessEvent::newFromVendTable(vendTable, previousBlocked, false).send();
}
}
public static void raiseForNewVendor(VendTable _vendTable)
{
if (_vendTable.Blocked == CustVendorBlocked::No)
{
return;
}
if (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeVendorHoldStatusChangedBusinessEvent)))
{
MGCLatticeVendorHoldStatusChangedBusinessEvent::newFromVendTable(_vendTable, CustVendorBlocked::No, true).send();
}
}
Payload Contract
One row per [DataMember], in declaration order. Values are read from the VendTable record and the previous status in the contract's initialize().
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | VendTable.DataAreaId | Company ID of the vendor, in lower case (for example usmf) |
vendorAccount | Vendor Account | VendAccount | VendTable.AccountNum | Vendor account number |
vendorName | Vendor Name | str | VendTable.name() | Display method; the vendor's party name |
blockedStatus | Blocked Status | int | enum2int(VendTable.Blocked) | New CustVendorBlocked value: 0 = No, 1 = Invoice, 2 = All, 3 = Payment, 4 = Requisition, 5 = Never, 6 = PurchOrder |
blockedStatusName | Blocked Status Name | str | enum2Symbol(enumNum(CustVendorBlocked), enum2int(VendTable.Blocked)) | Enum symbol of the new value, for example All; No means the hold was released |
previousBlockedStatus | Previous Blocked Status | int | enum2int(orig().Blocked) | CustVendorBlocked value before the change, same numbering as blockedStatus; 0 (No) for a vendor created on hold |
previousBlockedStatusName | Previous Blocked Status Name | str | enum2Symbol(enumNum(CustVendorBlocked), enum2int(orig().Blocked)) | Enum symbol of the value before the change; No for a vendor created on hold |
isNewVendor | Is New Vendor | boolean | Set by the trigger | true when the vendor was just created on hold; false for a change to an existing vendor. Written as true/false |
crossReferences | Cross references | List of MGCLatticeCrossReferenceEventContract | MGCLatticeCrossReferenceEventHelper::listFor(VendTable) | Keys external systems hold for the vendor: one { externalSystem, externalKey } object per external system, ordered by external system, historical keys included; [] when it has none. See Cross-reference keys. |
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; it orders the events of one record, gaps are normal and permanent, see the README), correlationId (String - always Scope:Key; groups emissions that belong to one logical run and defaults to Event: plus a GUID unique to this emission) 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 does not join a run: correlationId is unique to each emission (it is not shared with the Vendor created or Vendor updated event raised by the same save).
Example Payload
{
"BusinessEventId": "MGCLatticeVendorHoldStatusChangedBusinessEvent",
"ControlNumber": 588305,
"EventId": "B7E2940C-1F6A-4D38-8C25-5A0D9E3F71C6",
"EventTime": "\/Date(1790250483000)\/",
"EventTimeIso8601": "2026-09-24T11:48:03.2427872Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{267AA7B5-650D-8C97-2A26-0110C5B238A2}",
"ParentContextRecordSubjects": [],
"eventSequence": 48402,
"correlationId": "Event:a5c3e9f0-72b4-4d1e-8f63-1b9d0c7e2a54",
"schemaVersion": 1,
"legalEntity": "usmf",
"vendorAccount": "US-104",
"vendorName": "Fabrikam Supplier",
"blockedStatus": 2,
"blockedStatusName": "All",
"previousBlockedStatus": 1,
"previousBlockedStatusName": "Invoice",
"isNewVendor": false,
"crossReferences": [
{
"externalSystem": "LEGACYERP",
"externalKey": "V-20417"
}
]
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Vendor hold status changed") or by filtering the Category column to Accounts payable. 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 required.