Skip to main content

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 classMGCLatticeVendorPaymentPostedBusinessEvent
Contract classMGCLatticeVendorPaymentPostedBusinessEventContract
CategoryAccounts payable
ModuleModuleAxapta::Vendor
Label IDsVendorPaymentJournalPostedBusinessEventName / VendorPaymentJournalPostedBusinessEventDescription
TriggerLedgerJournalCheckPost.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:
    1. The journal header returned by parmLedgerJournalTable() has Posted == Yes and JournalType == LedgerJournalType::Payment (the Vendor disbursement journal type used by vendor payment journals). Customer payment journals and all other journal types are ignored.
    2. The enablement gate is checked once per journal, before any line is read.
    3. The journal's LedgerJournalTrans lines 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.
  • Direction: paymentAmount is 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). debitCredit states the same side as Debit or Credit. 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 by journalId (each emission has its own correlationId).
  • 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 fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaIdLedgerJournalTable.DataAreaIdCompany ID of the journal, in lower case (for example usmf)
journalIdJournal IDLedgerJournalIdLedgerJournalTable.JournalNumShared by all events of one journal
voucherVoucherVoucherLedgerJournalTrans.VoucherVoucher of the line
vendorAccountVendor AccountVendAccountLedgerJournalTrans.parmAccount(), or parmOffsetAccount() for an offset vendorThe vendor account number
vendorLegalEntityVendor Legal EntityDataAreaIdLedgerJournalTrans.Company, or OffsetCompany for an offset vendor; the journal's company when emptyCompany the vendor account belongs to, in lower case; differs from legalEntity only on an intercompany line
isOffsetAccountIs Offset AccountbooleanSet by the triggertrue when the vendor is the line's offset account; false when it is the line's account. Written as true/false
paymentAmountPayment AmountAmountCurAmountCurDebit - AmountCurCredit for an account vendor; AmountCurCredit - AmountCurDebit for an offset vendorAmount 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
debitCreditDebit Or CreditstrSign of paymentAmountDebit or Credit: the side the vendor account is posted on (Credit exactly when paymentAmount is negative)
currencyCodeCurrency CodeCurrencyCodeLedgerJournalTrans.CurrencyCodeTransaction currency of the line
paymentReferencePayment ReferencestrLedgerJournalTrans.PaymReferencePayment reference of the line (for check payments, the check number); may be empty
paymentModePayment ModestrLedgerJournalTrans.PaymModeMethod of payment code, for example CHECK or ELECTRONIC
transDateTransaction DateTransDateLedgerJournalTrans.TransDatePosting date of the line; "\/Date(ms)\/" form
crossReferencesCross referencesList of MGCLatticeCrossReferenceEventContractMGCLatticeCrossReferenceEventHelper::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.