Vendor payment posted
Fires after a vendor payment journal is posted, once for each vendor posting of the journal: a line whose account is a vendor, and a line whose offset account is a vendor. Each event carries the journal ID, voucher, vendor, the amount signed from the vendor's side with its debit or credit side, currency, method of payment, payment reference and transaction date. Downstream systems can use it to send remittance advice, update the cash position in treasury tools, mark invoices as paid in P2P or AP automation platforms, or reconcile with the bank.
| Event class | MGCLatticeVendorPaymentPostedBusinessEvent |
| Contract class | MGCLatticeVendorPaymentPostedBusinessEventContract |
| Category | Accounts payable |
| Module | ModuleAxapta::Vendor |
| Label IDs | VendorPaymentJournalPostedBusinessEventName / VendorPaymentJournalPostedBusinessEventDescription |
| Trigger | LedgerJournalCheckPost.runInternalPostJournalTransactionEnded delegate -> posted journal of type LedgerJournalType::Payment, one event per vendor posting: a line raises one event for a vendor on its account and one for a vendor on its offset account, so a line with a vendor on both sides raises two |
Trigger / Hook
- Handler class:
MGCLatticeVendorPaymentPostedEventHandlers; the method subscribes to a posting delegate with[SubscribesTo(classStr(LedgerJournalCheckPost), delegateStr(LedgerJournalCheckPost, runInternalPostJournalTransactionEnded))]. - Method signature:
public static void postJournalTransactionEnded(LedgerJournalCheckPost _ledgerJournalCheckPost). - When the delegate runs:
LedgerJournalCheckPost.runInternal()raises it immediately after the journal-posting transaction ends. It is also raised after runs that roll back (simulated posting, or a failed round of a post-and-transfer-errors run); those are ignored because the journal is not marked posted. - Firing conditions:
- The journal header returned by
parmLedgerJournalTable()hasPosted == YesandJournalType == LedgerJournalType::Payment(the Vendor disbursement journal type used by vendor payment journals). Customer payment journals and all other journal types are ignored. - The enablement gate is checked once per journal, before any line is read.
- The journal's
LedgerJournalTranslines are read in line-number order, and one event is sent for each vendor posting: for a line whose account type is Vendor (AccountType == LedgerJournalACType::Vend,isOffsetAccount=false), and for a line whose offset account type is Vendor (OffsetAccountType == LedgerJournalACType::Vend,isOffsetAccount=true). A line with a vendor on both sides raises two events. Lines without a vendor on either side raise nothing, and lines moved to an error journal by a transfer-errors posting are no longer in the journal and raise nothing.
- The journal header returned by
- Direction:
paymentAmountis signed from the vendor's side, the way D365 signs vendor transactions: positive when the vendor account is debited (a payment to the vendor), negative when it is credited (for example a refund received from the vendor, or a negative payment).debitCreditstates the same side asDebitorCredit. A vendor on the offset account is posted on the opposite side of the line's debit or credit column. - A journal with several vendor postings raises several events that share
journalId; group them byjournalId(each emission has its owncorrelationId). - Timing: the events are sent after the posting transaction has committed, so a posting that fails raises nothing. If posting itself is run inside an outer transaction, delivery waits until that outer transaction commits.
- Enablement gate:
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeVendorPaymentPostedBusinessEvent)), so the event only sends when activated in the business events catalog.
[SubscribesTo(classStr(LedgerJournalCheckPost), delegateStr(LedgerJournalCheckPost, runInternalPostJournalTransactionEnded))]
public static void postJournalTransactionEnded(LedgerJournalCheckPost _ledgerJournalCheckPost)
{
LedgerJournalTable ledgerJournalTable = _ledgerJournalCheckPost.parmLedgerJournalTable();
LedgerJournalTrans ledgerJournalTrans;
if (ledgerJournalTable.Posted != NoYes::Yes || ledgerJournalTable.JournalType != LedgerJournalType::Payment)
{
return;
}
if (!BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeVendorPaymentPostedBusinessEvent)))
{
return;
}
while select ledgerJournalTrans
order by LineNum
where ledgerJournalTrans.JournalNum == ledgerJournalTable.JournalNum
&& (ledgerJournalTrans.AccountType == LedgerJournalACType::Vend
|| ledgerJournalTrans.OffsetAccountType == LedgerJournalACType::Vend)
{
if (ledgerJournalTrans.AccountType == LedgerJournalACType::Vend)
{
MGCLatticeVendorPaymentPostedBusinessEvent::newFromLedgerJournalTrans(ledgerJournalTable, ledgerJournalTrans, false).send();
}
if (ledgerJournalTrans.OffsetAccountType == LedgerJournalACType::Vend)
{
MGCLatticeVendorPaymentPostedBusinessEvent::newFromLedgerJournalTrans(ledgerJournalTable, ledgerJournalTrans, true).send();
}
}
}
Payload Contract
One row per [DataMember], in declaration order. Values are read in the contract's initialize() from the posted journal header (LedgerJournalTable) and the journal line (LedgerJournalTrans), from the side of the line the vendor is on.
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | LedgerJournalTable.DataAreaId | Company ID of the journal, in lower case (for example usmf) |
journalId | Journal ID | LedgerJournalId | LedgerJournalTable.JournalNum | Shared by all events of one journal |
voucher | Voucher | Voucher | LedgerJournalTrans.Voucher | Voucher of the line |
vendorAccount | Vendor Account | VendAccount | LedgerJournalTrans.parmAccount(), or parmOffsetAccount() for an offset vendor | The vendor account number |
vendorLegalEntity | Vendor Legal Entity | DataAreaId | LedgerJournalTrans.Company, or OffsetCompany for an offset vendor; the journal's company when empty | Company the vendor account belongs to, in lower case; differs from legalEntity only on an intercompany line |
isOffsetAccount | Is Offset Account | boolean | Set by the trigger | true when the vendor is the line's offset account; false when it is the line's account. Written as true/false |
paymentAmount | Payment Amount | AmountCur | AmountCurDebit - AmountCurCredit for an account vendor; AmountCurCredit - AmountCurDebit for an offset vendor | Amount in the line currency, signed from the vendor's side: positive when the vendor is debited (a payment to the vendor), negative when the vendor is credited |
debitCredit | Debit Or Credit | str | Sign of paymentAmount | Debit or Credit: the side the vendor account is posted on (Credit exactly when paymentAmount is negative) |
currencyCode | Currency Code | CurrencyCode | LedgerJournalTrans.CurrencyCode | Transaction currency of the line |
paymentReference | Payment Reference | str | LedgerJournalTrans.PaymReference | Payment reference of the line (for check payments, the check number); may be empty |
paymentMode | Payment Mode | str | LedgerJournalTrans.PaymMode | Method of payment code, for example CHECK or ELECTRONIC |
transDate | Transaction Date | TransDate | LedgerJournalTrans.TransDate | Posting date of the line; "\/Date(ms)\/" form |
crossReferences | Cross references | List of MGCLatticeCrossReferenceEventContract | MGCLatticeCrossReferenceEventHelper::listFor(LedgerJournalTrans) | Keys external systems hold for the journal line the vendor is posted on: one { externalSystem, externalKey } object per external system, ordered by external system, historical keys included; [] when it has none. See Cross-reference keys. These are journal line keys, not the vendor's. |
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, even for lines of the same journal,.
Example Payload
A check payment to vendor 1001, entered with the vendor on the line's account and the bank on its offset account.
{
"BusinessEventId": "MGCLatticeVendorPaymentPostedBusinessEvent",
"ControlNumber": 588417,
"EventId": "E14B7A2D-3C90-4F6E-9D58-0B2C8F4A61D7",
"EventTime": "\/Date(1790257286000)\/",
"EventTimeIso8601": "2026-09-24T13:41:26.1704632Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{B427CF27-740C-B00B-EC28-143EC175D289}",
"ParentContextRecordSubjects": [],
"eventSequence": 48466,
"correlationId": "Event:7d0f3b2a-e6c1-4a95-b842-9f5e1c3d0a68",
"schemaVersion": 1,
"legalEntity": "usmf",
"journalId": "00612",
"voucher": "APPM001247",
"vendorAccount": "1001",
"vendorLegalEntity": "usmf",
"isOffsetAccount": false,
"paymentAmount": 2117.85,
"debitCredit": "Debit",
"currencyCode": "USD",
"paymentReference": "792",
"paymentMode": "CHECK",
"transDate": "\/Date(1790208000000)\/",
"crossReferences": [
{
"externalSystem": "LEGACYERP",
"externalKey": "PAY-2026-0918-3"
}
]
}
The same payment entered the other way round, with the bank on the line's account (credit 2117.85) and the vendor on its offset account, raises the event with "isOffsetAccount": true, "paymentAmount": 2117.85 and "debitCredit": "Debit". A refund received from the vendor is a credit on the vendor: "paymentAmount" is negative and "debitCredit" is "Credit".
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Vendor payment posted") 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.