Business event reference
The Lattice model publishes 81 business events across 16 catalog categories. They fill gaps in the Dynamics 365 Finance and Operations event catalog. Each event has its own page: the business moment that raises it, the payload it emits, an example payload, and how to activate it.
See the business events operator guide for discovering, activating and monitoring events.
What every payload contains
- The platform envelope. Every business event payload carries the D365 fields
BusinessEventId,BusinessEventLegalEntity,ContextRecordSubject,ControlNumber,EventId,EventTime,EventTimeIso8601,InitiatingUserAADObjectId,MajorVersion,MinorVersionandParentContextRecordSubjects. - The Lattice envelope. Every Lattice event adds three fields, inherited from
MGCLatticeBusinessEventContractBase:eventSequenceincreases across the whole catalog and all companies. It is allocated when the event is raised inside the business transaction, not when that transaction commits, so two long transactions can commit in the opposite order of their values. Use it to order the events of one business record (for example one sales order); do not re-sequence the whole stream by it. Gaps are permanent and normal: a value is not reused when the transaction that raised the event rolls back, when a publish fails, or when a payload is built only to describe the schema (catalog schema download, sample payloads). Only events that opt in to Lattice business event logging and support replay are re-published later under their original value; in this release that is Retail assortment published. For every other event a gap can also be an event that was never delivered. Restoring the database to an earlier point in time rewinds the counter.correlationIdgroups the emissions that belong to one logical run and is alwaysScope:Key:BatchTask:<id>for events raised by one batch task,BOMCalc:<id>,InventClose:<company>:<voucher>orMRPRun:<plan>for the run-scoped events, andEvent:<guid>(a lower-case GUID without braces, unique to the emission) for an event that is not part of a run.schemaVersionis the version of the payload shape, 1 for every event in this release. Members are only added within a version; a payload whose shape changes gets a higherschemaVersion. Consumers should ignore members they do not know. The platform fieldsMajorVersionandMinorVersionare always 0.
- How values are written. Every company identifier in the Lattice part of the payload (
legalEntity,vendorLegalEntity, ...) is lower case (usmf); the platform fieldBusinessEventLegalEntitykeeps the case of the session and can beUSMF. Enums are written as an integer member plus a...Namemember holding the enum symbol. Flags are JSONtrue/false. Lists of values are JSON arrays. Dates and date-times are written as\/Date(milliseconds)\/(milliseconds since 1970-01-01 UTC; a date is midnight UTC of that day; an empty date is\/Date(-2208988800000)\/). Amounts with a currency member beside them are in that currency; an amount without one ends inMSTand is in the accounting currency of the legal entity. Members ending inRecIdor namedinventDimIdare internal ids that differ between environments. Do not rely on the order of members in the JSON. - Shared payloads. The six trade agreement price events share one payload (with
changeType/changeTypeNamenaming the change), and the picking list canceled and transfer order picking list created events share another. Members that only some events of a family fill are empty for the others; each event page lists them.
Before you activate
Most events need nothing but activation in the business events catalog. These need more, or behave in ways worth knowing first:
- Need setup or a scheduled job. Sales price agreement effective and Sales price agreement expired come from the trade agreement effectivity batch, and Workflow stalled or failed from the workflow alert scan; a system administrator has to schedule both, and only one scan job, because the scan remembers what it has reported. Integration item parked fires for a retryable failure only when the scheduled dead-letter retry sweep has used up its retries. Custom ad-hoc business event is raised only by your own X++ code.
- Raised together. Invoicing a return order posts a credit note that raises both Sales order invoice posted and Return credit note posted; de-duplicate on the invoice number if you subscribe to both. Warehouse work completed fires for every closed work, including the picking work that also raises Warehouse picking completed. Sales price trade agreement changed (a sales price row written) and the trade agreement price events (a price journal posted or a row deleted) can describe the same change; the Accounts receivable parameters can switch the row-level alerts off.
- Not every path raises them. Batch job status changed covers status changes made through the batch job record, not the batch server's own start and finish transitions, and leaves out jobs that match an enabled batch job event filter. Bank reconciliation completed covers the classic bank reconciliation and Advanced bank reconciliation completed covers Advanced bank reconciliation; activate both if some bank accounts use each. MRP run completed covers the built-in planning engine, not Planning Optimization. DMF execution completed mostly misses imports run without batch. Retail info-code response captured might not fire for responses uploaded from stores. Business event publish failed covers only events that opt in to Lattice business event logging; in this release that is Retail assortment published.
- High volume. Inventory on-hand changed, Item back in stock and Workflow lifecycle action can fire many times a minute on a busy system.
Each event's page states its own conditions and limitations in full.
Accounts payable
| Event | Trigger | Description |
|---|---|---|
| Vendor ASN line changed | WHSASNItem.insert()/update() -> purchase ASN line (Module == Purch) created or its quantity changed | Fires per purchase ASN line with PO, shipment, load, item, quantity, unit and batch expiry, and again with previousQuantity when its quantity changes; outbound ASN lines do not raise it. |
| Vendor check issued | BankChequeTable.update() -> status Created/NotPrinted/Testprint -> Payment, RecipientType == Vend | Fires when a vendor check is issued (normally at check generation); a check returning to Payment from Cancel pending does not raise it again. |
| Vendor created | VendTable.insert() | Fires for every new vendor record, with account, name, group and currency. |
| Vendor hold status changed | VendTable Updated data event -> Blocked changed; VendTable.insert() -> new vendor on hold | Fires when a vendor's hold is applied, changed or released, or a vendor is created on hold, carrying the new and previous status. |
| Vendor payment posted | LedgerJournalCheckPost.runInternalPostJournalTransactionEnded -> posted LedgerJournalType::Payment journal | Fires after a vendor payment journal commits, once per vendor posting (account or offset), with the amount signed from the vendor's side, its debit/credit side, journal, voucher, vendor, payment method, reference and date. |
| Vendor updated | VendTable.update(boolean, boolean) | Fires after every vendor record update (no change filter), carrying account, name, group and currency. |
Accounts receivable
| Event | Trigger | Description |
|---|---|---|
| Collection case changed | CustCollectionsCaseDetail::createCase() / closeCase() | Fires when a collections case is opened or closed from the Collections page, in the case's own legal entity, carrying customer, case ID, status, action and current balance. |
| Collection letter posted | CustCollectionLetterPost.writeCustCollectionLetterJour() for a posted letter | Fires when Post collection letters posts a letter, carrying customer, letter number and level, letter and due dates, fee, posting date and fee voucher. |
| Customer created | CustTable.insert() | Fires when a customer master record is inserted; the customer resync job also emits it for every existing customer. |
| Customer credit limit exceeded | CustCreditLimit.check() returns false | Fires every time the standard credit limit check finds the customer's balance plus the document being checked over the credit limit in effect (agreement, credit management or customer limit), with that limit and all amounts in accounting currency. |
| Customer hold status changed | CustTable.update() when Blocked changes | Fires when a customer's hold status (Blocked) changes value, carrying the new and the previous status and the credit limit in accounting currency. |
| Customer updated | CustTable.update() | Fires on every save of an existing customer master record, whether or not a field changed. |
| Dunning level changed | CustCollectionLetterJour.updateCollectionLetterCodeCustTrans() when an open transaction's level changes | Fires once per open customer transaction whose collection letter level a letter changes (normally when it is posted), with the previous and new level; the fee line is not reported. |
| E-invoice submission status changed | BusinessDocumentSubmission Inserted, or Updated when State changes | Fires when an Electronic invoicing submission is logged (first status) and each time its status changes, carrying legal entity, source document, status and UTC submission time. |
| Free text invoice posted | CustInvoiceJour.insert() with an empty SalesId, not pro forma | Fires when a free text invoice or free text credit note is posted, carrying invoice number, invoice account, date, the amount in transaction and in accounting currency, and the currency; pro forma printouts do not raise it. |
Asset management
| Event | Trigger | Description |
|---|---|---|
| Maintenance work order completed | EntAssetLifecycleStateUpdate_WorkOrderTable.updateStateRecord() -> new state has Set actual end | Fires when an existing work order enters a state that stamps its actual end (Completed in USMF); takes precedence over Started and Scheduled. |
| Maintenance work order created | EntAssetWorkOrderTable Inserted data event -> raised when the creating transaction commits | Fires once per new work order as its creating transaction commits, with its lines' assets, functional location and first lifecycle state. |
| Maintenance work order scheduled | EntAssetLifecycleStateUpdate_WorkOrderTable.updateStateRecord() -> new state is the lifecycle model's scheduled state | Fires when a work order moves to its lifecycle model's scheduled state, as standard scheduling does (Scheduled in USMF). |
| Maintenance work order started | EntAssetLifecycleStateUpdate_WorkOrderTable.updateStateRecord() -> new state has Set actual start (and not Set actual end) | Fires when an existing work order enters a state that stamps its actual start (InProgress in USMF), carrying its assets and actual start. |
Bank management
| Event | Trigger | Description |
|---|---|---|
| Advanced bank reconciliation completed | BankReconciliationHeader::reconcile() / reconcileInBatch() when IsReconciled goes from No to Yes | Fires when an Advanced bank reconciliation worksheet is marked as reconciled, with its statements and the counts and bank-currency totals of the matched statement lines and bank transactions; the classic bank account reconciliation does not raise it. |
| Bank reconciliation completed | BankAccountReconcile.updateBankAccountStatementToReconciled() when ReconcileDate is first set | Fires when a bank statement is reconciled with the classic Reconcile account action, with the count and accounting-currency total of the reconciled transactions; Advanced bank reconciliation does not raise it. |
Fixed assets
| Event | Trigger | Description |
|---|---|---|
| Fixed asset acquisition posted | AssetTrans.insert() -> TransType == Acquisition | Fires for every acquisition row written, one per book, with isReversal/isReclassification flagging rows from a reversal or reclassification. |
| Fixed asset disposal posted | AssetTrans.insert() -> TransType == DisposalSale/DisposalScrap | Fires for every sale or scrap disposal row written, one per book, with isReversal/isReclassification flagging rows from a reversal or reclassification. |
General ledger
| Event | Trigger | Description |
|---|---|---|
| General journal posted | LedgerJournalCheckPost.runInternalPostJournalTransactionEnded delegate | A general (Daily) journal is posted. |
Inventory management
| Event | Trigger | Description |
|---|---|---|
| External item changed | CustVendExternalItem Inserted/Updated data events, plus MGCLatticeProcessAllExternalItems resync | Fires on every create or update of a customer or vendor external item cross-reference, with the external number, text, account or group, and module type. |
| Inventory closing completed | InventCostLedgerPostingTask.updateLedgerPosting() | Fires when an inventory close or recalculation has finished (settlement, adjustments and ledger posting done), with the closing date, voucher and which of the two ran. |
| Inventory on-hand changed | InventUpdateOnhand.onOnHandUpdated, sent after commit | Fires after a transaction commits, once per item, site and warehouse whose net physical on-hand it changed, with the change and that site and warehouse's on-hand quantities (high volume). |
| Item back in stock | InventUpdateOnhand.onOnHandUpdated, sent after commit | Fires after a transaction commits when available physical for an item in a site and warehouse moved from 0 or less to above 0, at most once per item, site and warehouse per transaction. |
| Picking list canceled | WMSPickingRoute.cancel() / WMSOrderTransType_Output_OrderPick.cancel() | Fires when canceling a picking list cancels at least one line, or when a single picking line is canceled, listing only the lines that were canceled. |
| Quality order failed | InventQualityOrderValidate.run() | Fires when quality order validation moves the order to Fail, with quantities, disposition code and validator. |
| Quality order passed | InventQualityOrderValidate.run() | Fires when quality order validation moves the order to Pass. |
| Released product created | EcoResProductReleaseManager.createInventTable() / EcoResProductReleaseManagerItem.release(), plus MGCLatticeProcessAllItems resync | Fires when a product is released to a legal entity, in that legal entity, with item, product RecId, search name, item type and lifecycle state. |
| Transfer order picking list created | InventTransferUpdPick.run() | Fires when Generate picking list creates a picking list for a transfer order, listing the lines to pick and whether the list is activated or already complete. |
| Transfer order received | InventTransferUpdReceive.run() | Fires when a transfer order becomes fully received. |
| Transfer order shipped | InventTransferUpdShip.run() | Fires when a transfer order becomes fully shipped. |
Master planning
| Event | Trigger | Description |
|---|---|---|
| MRP run completed | ReqCalc.run() | Fires once when a built-in master planning run finishes calculating a plan (main thread of a ReqCalcScheduleItemTable run only; helper threads, forecast scheduling, firming, explosions and canceled runs don't raise it); never for Planning Optimization runs. |
Product information management
| Event | Trigger | Description |
|---|---|---|
| Retail assortment published | RetailAssortmentLookupTask.run() | Fires when Process assortments publishes an assortment for the first time in a legal entity or its effective item list there changes, with the items and price at publish. |
Production control
| Event | Trigger | Description |
|---|---|---|
| BOM calculation completed | BomCalcItem.insertBOMCalcTable() | Fires when an item price (BOM) calculation saves its top-level result for an item, with the calculated cost and calculation date. |
| BOM version approved | BomVersionApprove.run() | Fires when the BOM version approval operation (Approve action, engineering change case, or OData approval action) approves a version that was not approved before. |
| Production order ended | ProdUpdHistoricalCost.run() | Fires when the End update moves a production order to Ended (Completed), with the order's total good and error quantities reported as finished and their unit. |
| Production order estimated | ProdUpdCostEstimation.run() | Fires when a cost estimation moves a production order from Created to Estimated. |
| Production order reported as finished | ProdUpdReportFinished.run() / ProdJournalCheckPostProd.run() | Fires once when a report-as-finished update or a report-as-finished journal posted on its own moves a production order to Reported as finished, with the order's total good and error quantities and their unit. |
| Production order scheduled | ProdUpdScheduling.run() / ReqTransPoMarkFirm.insertMovement() | Fires when operations or job scheduling first moves a production order to Scheduled, or when firming a planned order creates it as Scheduled. |
| Production order started | ProdUpdStartUp.run() | Fires when a start update first moves a production order to Started. |
| Production pick list posted | ProdJournalCheckPostBOM.run() | Fires once per posted production picking list journal, listing each posted component line with its own quantity and inventory unit. |
| Production route card posted | ProdJournalCheckPostRoute.run() | Fires once per posted production route card journal, listing each posted line with its operation, resource, hours and good and error quantities. |
Projects
| Event | Trigger | Description |
|---|---|---|
| Project invoice posted | ProjInvoiceJour.insert() | A project invoice journal header is created during posting, carrying the invoice total and currency; pro forma invoices are excluded. |
| Project task assigned | ProjTask::reserveResourceActivity() (work breakdown structure), plus ProjActivityAssignment Inserted/Updated data events | Fires once per resource holding hours on a project task when a published plan's task resources are saved or a draft plan is published (a resource that stays on the task is reported again), carrying project, task, resource, worker and assigned hours; ProjActivityAssignment rows written by customizations and imports also raise it. |
Purchase orders
| Event | Trigger | Description |
|---|---|---|
| Purchase agreement confirmed | AgreementConfirm_Purch.createHeaderHistory() | Fires on every purchase agreement confirmation, when the agreement header history snapshot is created, with the agreement's buying legal entity. |
| Purchase requisition approved | PurchReqWFStatusTransitionHelper::setPurchReqTable2Approved() | Fires when a purchase requisition's approval workflow completes and the requisition is set to Approved, with the buying legal entities of its lines. |
| Purchase return order created | PurchTableType_ReturnItem.inserted() | Fires when a purchase order header of type Returned order is inserted. |
Retail
| Event | Trigger | Description |
|---|---|---|
| Retail info-code response captured | RetailTransactionInfocodeTrans.insert() | Fires when an info code (reason code) response row is inserted in headquarters, with code, subcode, text, amount, transaction and parent line, store, terminal, staff and UTC capture time; store uploads might not raise it. |
Sales orders
| Event | Trigger | Description |
|---|---|---|
| Return credit note posted | SalesInvoiceJournalPost.postJournalPost() for a posted return order invoice (SalesType ReturnItem) | Fires when a return order is invoiced (its credit note is posted), carrying credit note number, return order, RMA number, customer, amount and currency. |
| Return order received | Return registration (SalesLine.returnLineRegisterUpdate() / SalesReturnNonStockedRegistrationHelper.updateOnRegistration()) when ReturnStatus moves to Registered | Fires when a return order line is registered (goods arrived), carrying RMA number, item, quantity, disposition code and receiving warehouse. |
| Sales agreement confirmed | AgreementConfirm_Sales.createHeaderHistory() | A sales agreement is confirmed and its history snapshot written, in the agreement's selling legal entity; every re-confirmation fires again. |
| Sales order canceled | SalesUpdateRemain.cancelOpenOrderLinesDeliveryRemainder() / cancelMultipleOrdersDeliveryRemainder() / MCRSalesOrderCancellation::cancelOrder() / cancelOrderFTC() | A sales order's remaining lines are canceled and its status becomes Canceled; from the Cancel action only once the cancellation is final. Return orders raise it too, with the order type and RMA number. |
| Sales order confirmed | SalesConfirmJournalPost.postJournalPost() | A sales order confirmation journal is posted, carrying the confirmation total; pro forma confirmations are excluded. |
| Sales order invoice posted | SalesInvoiceJournalPost.postJournalPost() for a posted, non-pro-forma invoice with a SalesId | Fires once per posted sales order invoice or credit note, after the totals are written, carrying invoice account, order, invoice number, date, amount and currency; pro forma invoices do not raise it. |
| Sales order line changed | SalesLine Updated when quantity, price or requested dates change | Fires when a sales order line's quantity, price, requested receipt date or requested ship date changes, with before and after values. |
| Sales packing slip posted | SalesPackingSlipJournalPost.postJournalPost() | A sales packing slip journal is posted; pro forma packing slips, cancellations and return order receipts are excluded. |
| Sales price agreement created | PriceDiscAdmCheckPost.createOrUpdatePriceDiscTableRecords() | A posted journal line inserted a sales price for an agreement (incl. quantity break and dimensions) with no sales price in effect on its From date. |
| Sales price agreement deleted | PriceDiscTable.delete() / PriceDiscAdmTable::deleteMarked() | A sales price is deleted by a user, by code or by a posted journal line marked for deletion. |
| Sales price agreement effective | MGCLatticeTradeAgreementPriceEffectivityBatch.run() | The scheduled batch reports, once and in date order, each sales price whose From date was reached since its last position. |
| Sales price agreement expired | MGCLatticeTradeAgreementPriceEffectivityBatch.run() | The scheduled batch reports each sales price whose To date passed when no other price of the same agreement is in effect the next day. |
| Sales price agreement modified | PriceDiscAdmCheckPost.createOrUpdatePriceDiscTableRecords() | A posted journal line changed at least one value of an existing sales price, carrying the old amount and the changed fields. |
| Sales price agreement superseded | PriceDiscAdmCheckPost.createOrUpdatePriceDiscTableRecords() | A posted journal line added a sales price for an agreement that had a price in effect on its From date, carrying that price's amount and record ID. |
| Sales price trade agreement changed | PriceDiscTable Inserted/Updated with relation PriceSales | Record-level signal for any insert or update of a sales-price agreement row, carrying its unit, inventory dimensions and record ID; it fires for journal postings only when the Accounts receivable "Skip price agreement insert/update alerts" parameters are No. |
| Sales quotation confirmed | SalesQuotationEditLinesForm_Sales_Confir.createJournal() | A sales quotation is confirmed, carrying the quotation number, the confirmation number and the sales order it created. |
| Sales return order created | SalesTableType_ReturnItem.inserted() | A sales return order (SalesTable of type ReturnItem) is created. |
System administration
| Event | Trigger | Description |
|---|---|---|
| Aggregate measurement refresh completed | BIMeasurementProcessingStatisticsFull.update() | Fires when a full-reset entity store measurement refresh finishes (succeeded or failed), with measurement, status, start/completion times and duration. |
| Batch job status changed | BatchJob.update() | Fires when a batch job has its status changed through the job record (for example Change status on the Batch jobs page), in the job's company, unless an enabled batch job event filter matches the job; the batch server's own start/finish transitions do not raise it. |
| Business event publish failed | BusinessEventsPublisher.publish() | Fires when publishing an opted-in Lattice event fails outside a database transaction and an Error row is written to the business event log; in this release only Retail assortment published opts in. |
| Custom ad-hoc business event | MGCLatticeCustomAdHocEventApi::raiseEvent() | Raised only by customer X++ code calling the ad-hoc X++ API (not an OData action), carrying a caller-chosen event name, a record reference and a free-form JSON payload. |
| DMF execution completed | DMFDefinitionGroupExecution.update() / DMFEntityDbExporter::generateEntityDBDataV2() | Fires per entity when a batch import, copy to target, file export or bring-your-own-database export sets the target status to Finished, Error or Canceled, with the company, a derived outcome and record/error counts; an import run without batch mostly isn't reported. |
| Integration item parked | MGCLatticeIntegrationDlqAlert.onParked() | Fires when a failed Lattice async operation or file ingestion item is parked in the dead-letter queue, immediately (non-retryable) or when the scheduled retry sweep uses up its attempts; without that sweep, retryable failures never raise it. |
| Recurring data job health changed | MGCLatticeRecurringJobHealthAlerts background process (every 15 minutes) and health refreshes on the recurring integration pages | Fires once when the health of a recurring data job with alerts on changes (for example Healthy to Failing, or Broken back to Healthy), in the company of the job's processing batch job, with the previous and new health, the reasons and the signal values; a job's first evaluation and unchanged health raise nothing. |
| Workflow lifecycle action | WorkflowTrackingCommentTable.insert() | Fires for each reportable workflow action (assigned, submitted, approved, rejected, delegated, escalated, completed, failed and so on) with workflow, document, step, acting user, new assignee and comment. |
| Workflow stalled or failed | MGCLatticeWorkflowStalledAlertBatch.run() | Raised by a scheduled scan once for each faulted or unrecoverable workflow instance and each work item pending past the threshold (default 24 h), and again only when the condition changes or comes back; each condition is raised in its document's company. |
Warehouse management
| Event | Trigger | Description |
|---|---|---|
| Warehouse load shipped | WHSShipConfirm.shipConfirm() / WHSShipConfirmationActionTransportLoad.shipConfirm() | Fires when load ship confirmation or transport load ship confirmation (including the mobile app's transport loading flow) moves a load to Shipped, once per load. |
| Warehouse picking completed | WHSWorkTable.update() | Fires when sales, transfer, production or kanban picking work closes, with the work order type and one entry per completed pick line (item, quantity, unit, location, operator). |
| Warehouse work completed | WHSWorkTable.update() / WHSWorkTable.insert() | Fires when warehouse work closes or is created already closed, with the work type, order, load and shipment references, and close time. |