Skip to main content

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 classMGCLatticeVendorHoldStatusChangedBusinessEvent
Contract classMGCLatticeVendorHoldStatusChangedBusinessEventContract
CategoryAccounts payable
ModuleModuleAxapta::Vendor
Label IDsVendorHoldStatusChangedBusinessEventName / VendorHoldStatusChangedBusinessEventDescription
TriggerVendTable 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's Blocked value differs from its orig() value, in either direction, and reports the orig() value as the previous status. Saves that leave Vendor hold unchanged are ignored.
    • New vendors: public static void raiseForNewVendor(VendTable _vendTable), called by the VendTable.insert() wrapper MGCLatticeVendTable_Table_Extension after the vendor is inserted. It fires when the new vendor's Blocked is anything but No, with No as the previous status and isNewVendor = 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.
  • 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 after update() returns; when both events are active, one save produces Vendor hold status changed first (lower eventSequence), 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 fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaIdVendTable.DataAreaIdCompany ID of the vendor, in lower case (for example usmf)
vendorAccountVendor AccountVendAccountVendTable.AccountNumVendor account number
vendorNameVendor NamestrVendTable.name()Display method; the vendor's party name
blockedStatusBlocked Statusintenum2int(VendTable.Blocked)New CustVendorBlocked value: 0 = No, 1 = Invoice, 2 = All, 3 = Payment, 4 = Requisition, 5 = Never, 6 = PurchOrder
blockedStatusNameBlocked Status Namestrenum2Symbol(enumNum(CustVendorBlocked), enum2int(VendTable.Blocked))Enum symbol of the new value, for example All; No means the hold was released
previousBlockedStatusPrevious Blocked Statusintenum2int(orig().Blocked)CustVendorBlocked value before the change, same numbering as blockedStatus; 0 (No) for a vendor created on hold
previousBlockedStatusNamePrevious Blocked Status Namestrenum2Symbol(enumNum(CustVendorBlocked), enum2int(orig().Blocked))Enum symbol of the value before the change; No for a vendor created on hold
isNewVendorIs New VendorbooleanSet by the triggertrue when the vendor was just created on hold; false for a change to an existing vendor. Written as true/false
crossReferencesCross referencesList of MGCLatticeCrossReferenceEventContractMGCLatticeCrossReferenceEventHelper::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.