PEPPOL Sales Documents – Extension Points

The RSMSTA SalesInvPEPPOLBIS3.0 (XMLport 10058600) and RSMSTA SalesCrMemoPEPPOLBIS3.0 (XMLport 10058601) XMLports generate outbound PEPPOL BIS 3.0 sales invoices and credit memos. Both XMLports share a nearly identical structure and expose the same extension points unless noted otherwise.

There are three layers of extension points:

  • XMLport events — declared directly on the XMLport (namespace WiseCourier.Standard.eDocuments)
  • Courier events — raised by helper codeunits called during XMLport execution
  • PEPPOL Management events — standard BC events (namespace Microsoft.Sales.Peppol) that the XMLport delegates to for most data retrieval

Subscribe to these events in your own codeunit — no modification of the base XMLport is required.

The XMLports are responsible only for XML generation. Data validation does not occur inside the XMLport — it happens afterwards when the XML is submitted through the web service for delivery. Subscribers to these events can add or modify data freely without triggering validation errors at generation time.


XMLport events

OnBeforeAdditionalItemPropertyLoop

Fires once per line before writing AdditionalItemProperty elements. Call AddAdditionalItemPropertyToList() on the XMLport instance to inject name/value pairs that appear as <cac:AdditionalItemProperty> elements in the output.

This event uses [IntegrationEvent(true, false)] — the first parameter true means “include sender”, so the XMLport instance is the sender. You call AddAdditionalItemPropertyToList on that sender instance.

// Invoice
[EventSubscriber(ObjectType::XmlPort, XmlPort::"RSMSTA SalesInvPEPPOLBIS3.0",
    'OnBeforeAdditionalItemPropertyLoop', '', false, false)]
local procedure AddCustomItemProperties(SalesLine: Record "Sales Line")
var
    SalesInvXmlPort: XmlPort "RSMSTA SalesInvPEPPOLBIS3.0";
begin
    SalesInvXmlPort.AddAdditionalItemPropertyToList('Color', SalesLine."RSMSTA Color");
    SalesInvXmlPort.AddAdditionalItemPropertyToList('BatchNo', SalesLine."Lot No.");
end;

// Credit Memo — identical signature, different XMLport
[EventSubscriber(ObjectType::XmlPort, XmlPort::"RSMSTA SalesCrMemoPEPPOLBIS3.0",
    'OnBeforeAdditionalItemPropertyLoop', '', false, false)]
local procedure AddCustomItemPropertiesCrMemo(SalesLine: Record "Sales Line")
var
    SalesCrMemoXmlPort: XmlPort "RSMSTA SalesCrMemoPEPPOLBIS3.0";
begin
    SalesCrMemoXmlPort.AddAdditionalItemPropertyToList('Color', SalesLine."RSMSTA Color");
end;

OnGetFinalDueDateClaim (Invoice only)

Lets you override the <cbc:DueDate> element and/or inject an AdditionalDocumentReference entry with DocumentDescription = 'EINDAGI' (used for final due date claims in Icelandic invoicing).

ParameterDirectionEffect
ClaimDueDatevar Text (YYYYMMDD)Replaces <cbc:DueDate> in the XML
ClaimFinalDueDatevar Text (YYYYMMDD)Adds an AdditionalDocumentReference with ID = <date> and description EINDAGI
[EventSubscriber(ObjectType::XmlPort, XmlPort::"RSMSTA SalesInvPEPPOLBIS3.0",
    'OnGetFinalDueDateClaim', '', false, false)]
local procedure SetFinalDueDateClaim(
    SalesHeader: Record "Sales Header";
    var ClaimFinalDueDate: Text;
    var ClaimDueDate: Text)
begin
    if SalesHeader."Payment Method Code" = 'CLAIM30' then begin
        ClaimDueDate := Format(SalesHeader."Due Date" + 30, 0, '<Year4><Month,2><Day,2>');
        ClaimFinalDueDate := Format(SalesHeader."Due Date" + 60, 0, '<Year4><Month,2><Day,2>');
    end;
end;

Courier events

These events are raised by helper codeunits called during XMLport execution. Subscribe on RSMSTA Wise Courier Events or RSMSTA EDocIntegrationHelpFunc as indicated.

OnAfterGetDueDateForInvoice (Invoice only)

Raised after the due date is resolved. Modify DueDate as an ISO 8601 text string before it is written to the XML.

[EventSubscriber(ObjectType::Codeunit, Codeunit::"RSMSTA Wise Courier Events",
    'OnAfterGetDueDateForInvoice', '', false, false)]
local procedure AdjustDueDate(var DueDate: Text; SalesHeader: Record "Sales Header")
begin
    if SalesHeader."Currency Code" = 'USD' then
        DueDate := Format(SalesHeader."Due Date" + 5, 0, 9);
end;

CourierGetAccountingSupplierPartyPostalAddr

Override the supplier postal address (street, city, postal zone, country).

[EventSubscriber(ObjectType::Codeunit, Codeunit::"RSMSTA Wise Courier Events",
    'CourierGetAccountingSupplierPartyPostalAddr', '', false, false)]
local procedure OverrideSupplierAddress(
    SalesHeader: Record "Sales Header";
    var StreetName: Text; var AdditionalStreetName: Text;
    var CityName: Text; var PostalZone: Text;
    var CountrySubentity: Text; var IdentificationCode: Text;
    var DummyListID: Text)
begin
    if SalesHeader."Responsibility Center" = 'WAREHOUSE' then begin
        StreetName := 'Warehouse Road 1';
        CityName := 'Reykjavik';
        PostalZone := '108';
        IdentificationCode := 'IS';
    end;
end;

CourierGetAccountingSupplierPartyTaxSchemeBIS

Override CompanyID, schemeID, and TaxSchemeID inside PartyTaxScheme for the supplier party.

CourierOnBeforeGetAccountingSupplierPartyInfoByFormat / CourierOnAfterGetAccountingSupplierPartyInfoByFormat

Override or modify the supplier EndpointID, SchemeID, and Name before/after the standard lookup runs.


OnBeforeGetAdditionalDocRefInfoSalesInvoice / OnAfterGetAdditionalDocRefInfoSalesInvoice

Control AdditionalDocumentReference entries on invoices — attachments, embedded PDFs, external URIs. The OnBefore variant has an IsHandled parameter to skip the standard logic entirely.

OnBeforeGetAdditionalDocRefInfoSalesCrMemo / OnAfterGetAdditionalDocRefInfoSalesCrMemo

Same as above for credit memos.

OnBeforeCountAttachmentsSalesOrServiceInvoices / OnAfterCountAttachmentsSalesOrServiceInvoices

Control how many AdditionalDocumentReference loop iterations are created on invoices. Use this to add extra attachment slots beyond what the standard logic allocates.

OnBeforeCountAttachmentsCrMemoOrServiceInvoices / OnAfterCountAttachmentsCrMemoOrServiceInvoices

Same as above for credit memos.


OnAfterGetPaymentMeansPayeeFinancialAccBISInternal

Override the PayeeFinancialAccount (IBAN) and FinancialInstitutionBranch (SWIFT/BIC) inside the XMLport’s PaymentMeans section. This fires from the XMLport directly and takes precedence over the bank account lookup from the RSMSTA Use for e-Invoice flag.

[EventSubscriber(ObjectType::Codeunit, Codeunit::"RSMSTA EDocIntegrationHelpFunc",
    'OnAfterGetPaymentMeansPayeeFinancialAccBISInternal', '', false, false)]
local procedure OverrideBankAccountInternal(
    SalesHeader: Record "Sales Header";
    var PayeeFinancialAccountID: Text;
    var FinancialInstitutionBranchID: Text)
begin
    if SalesHeader."Currency Code" = 'EUR' then begin
        PayeeFinancialAccountID := 'IS140159260076545510730339';
        FinancialInstitutionBranchID := 'NBIIISREXX';
    end;
end;

OnBeforeCreateSalesInvoiceXML / OnAfterCreateSalesInvoiceXML

Intercept the entire XML generation pass for invoices.

  • OnBefore — fires before the XMLport runs. Set IsHandled := true to completely replace XML generation with your own logic.
  • OnAfter — fires after the XMLport has written the XML into a TempBlob. Post-process the blob directly (e.g., inject custom namespaces or elements via DOM manipulation).

OnBeforeCreateSalesCrMemoXML / OnAfterCreateSalesCrMemoXML

Same as above for credit memos.


PEPPOL Management events

The XMLports call PEPPOL Management (codeunit 1605) for most data retrieval. Subscribe to its events to override individual fields without touching the XMLports. These events apply to both invoices and credit memos unless noted.

Invoice header

OnAfterGetGeneralInfo — Note, TaxPointDate, AccountingCost, InvoiceTypeCode

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetGeneralInfo', '', false, false)]
local procedure SetInvoiceHeaderFields(
    SalesHeader: Record "Sales Header";
    var ID: Text; var IssueDate: Text; var InvoiceTypeCode: Text;
    var Note: Text; var TaxPointDate: Text;
    var DocumentCurrencyCode: Text; var AccountingCost: Text)
begin
    Note := SalesHeader."RSMSTA Invoice Note";
    TaxPointDate := Format(SalesHeader."Posting Date", 0, 9);
    AccountingCost := SalesHeader."Your Reference";
end;

OnAfterGetOrderReferenceInfo

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetOrderReferenceInfo', '', false, false)]
local procedure ForceOrderReference(
    SalesHeader: Record "Sales Header"; var OrderReferenceID: Text)
begin
    if SalesHeader."RSMSTA Original Order No." <> '' then
        OrderReferenceID := SalesHeader."RSMSTA Original Order No.";
end;

OnAfterGetBuyerReference

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetBuyerReference', '', false, false)]
local procedure SetBuyerReference(
    SalesHeader: Record "Sales Header"; var BuyerReference: Text)
begin
    if BuyerReference = '' then
        BuyerReference := SalesHeader."External Document No.";
end;

OnAfterGetContractDocRefInfo

Override ContractDocumentReference/ID and related fields.

OnAfterGetAdditionalDocRefInfo

Inject or modify AdditionalDocumentReference entries at the PEPPOL Management level (document attachments from the Document Attachment table).

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetAdditionalDocRefInfo', '', false, false)]
local procedure AttachDeliveryNoteURI(
    var AdditionalDocumentReferenceID: Text;
    var AdditionalDocRefDocumentType: Text;
    var URI: Text; var MimeCode: Text;
    var EmbeddedDocumentBinaryObject: Text;
    SalesHeader: Record "Sales Header";
    ProcessedDocType: Option Sale,Service;
    var DocumentAttachments: Record "Document Attachment";
    var FileName: Text)
begin
    if AdditionalDocumentReferenceID <> '' then
        exit;
    URI := 'https://docs.mycompany.com/delivery/' + SalesHeader."No.";
    AdditionalDocumentReferenceID := SalesHeader."No." + '-DN';
    AdditionalDocRefDocumentType := 'DeliveryNote';
end;

Supplier party

OnAfterGetAccountingSupplierPartyInfoByFormat — EndpointID, SchemeID

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetAccountingSupplierPartyInfoByFormat', '', false, false)]
local procedure SetSupplierEndpoint(
    var SupplierEndpointID: Text; var SupplierSchemeID: Text;
    var SupplierName: Text; IsBISBilling: Boolean)
begin
    if IsBISBilling and (SupplierSchemeID = '') then begin
        SupplierEndpointID := '5012345678901';
        SupplierSchemeID := '0088';
    end;
end;

OnAfterGetAccountingSupplierPartyLegalEntityByFormat

Override RegistrationName, CompanyID, and schemeID in PartyLegalEntity.

OnAfterGetAccountingSupplierPartyContact

Override contact name, telephone, fax, and email for the supplier.

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetAccountingSupplierPartyContact', '', false, false)]
local procedure SetSupplierContact(
    SalesHeader: Record "Sales Header";
    var ContactID: Text; var ContactName: Text;
    var Telephone: Text; var Telefax: Text; var ElectronicMail: Text)
begin
    ElectronicMail := 'invoicing@mycompany.com';
end;

Customer party

OnAfterGetAccountingCustomerPartyInfoByFormat — EndpointID, SchemeID

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetAccountingCustomerPartyInfoByFormat', '', false, false)]
local procedure OverrideCustomerEndpoint(
    SalesHeader: Record "Sales Header";
    var CustomerEndpointID: Text; var CustomerSchemeID: Text;
    var CustomerPartyIdentificationID: Text;
    var CustomerPartyIDSchemeID: Text; var CustomerName: Text;
    IsBISBilling: Boolean)
var
    Customer: Record Customer;
begin
    if Customer.Get(SalesHeader."Bill-to Customer No.") then
        if Customer."RSMSTA PEPPOL Endpoint" <> '' then begin
            CustomerEndpointID := Customer."RSMSTA PEPPOL Endpoint";
            CustomerSchemeID := Customer."RSMSTA PEPPOL Scheme";
        end;
end;

OnAfterGetAccountingCustomerPartyLegalEntityByFormat

Override RegistrationName, CompanyID, and schemeID in the customer PartyLegalEntity.

OnAfterGetAccountingCustomerPartyContact

Override customer contact name, telephone, fax, and email.


Invoice lines

OnAfterGetLineGeneralInfo — Note, quantity, extension amount, AccountingCost

The primary hook for writing <cbc:Note> on each line. By default the Note element contains the sales line type text — set it to '' to suppress that or replace it with your own value.

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetLineGeneralInfo', '', false, false)]
local procedure SetInvoiceLineFields(
    SalesLine: Record "Sales Line"; SalesHeader: Record "Sales Header";
    var InvoiceLineID: Text; var InvoiceLineNote: Text;
    var InvoicedQuantity: Text; var InvoiceLineExtensionAmount: Text;
    var InvoiceLineAccountingCost: Text)
begin
    if SalesLine."RSMSTA Line Comment" <> '' then
        InvoiceLineNote := SalesLine."RSMSTA Line Comment"
    else
        InvoiceLineNote := '';

    InvoiceLineAccountingCost := SalesLine."RSMSTA Cost Center";
end;

OnAfterGetLineItemInfo — Description, Name, item identifiers, origin country

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetLineItemInfo', '', false, false)]
local procedure EnrichItemInfo(
    SalesLine: Record "Sales Line";
    var Description: Text; var Name: Text;
    var SellersItemIdentificationID: Text;
    var StandardItemIdentificationID: Text; var StdItemIdIDSchemeID: Text;
    var OriginCountryIdCode: Text; var OriginCountryIdCodeListID: Text)
var
    Item: Record Item;
begin
    if SalesLine.Type = SalesLine.Type::Item then
        if Item.Get(SalesLine."No.") then begin
            if Item."Country/Region of Origin Code" <> '' then
                OriginCountryIdCode := Item."Country/Region of Origin Code";
            if Description = '' then
                Description := Item."Description 2";
        end;
end;

OnAfterGetLinePriceInfo — Unit price, base quantity, unit code

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetLinePriceInfo', '', false, false)]
local procedure AdjustLinePrice(
    SalesLine: Record "Sales Line"; SalesHeader: Record "Sales Header";
    var InvoiceLinePriceAmount: Text;
    var BaseQuantity: Text; var UnitCode: Text)
begin
    if SalesLine.Type = SalesLine.Type::"G/L Account" then
        UnitCode := 'HUR';  // hours
end;

Payment

OnAfterGetPaymentMeansInfo — Payment means code, payment ID, due date

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetPaymentMeansInfo', '', false, false)]
local procedure SetPaymentMeans(
    SalesHeader: Record "Sales Header";
    var PaymentMeansCode: Text; var PaymentMeansListID: Text;
    var PaymentDueDate: Text; var PaymentChannelCode: Text;
    var PaymentID: Text;
    var PrimaryAccountNumberID: Text; var NetworkID: Text)
begin
    PaymentID := SalesHeader."RSMSTA KID Number";
    if SalesHeader."Payment Method Code" = 'CARD' then
        PaymentMeansCode := '48';
end;

OnAfterGetPaymentMeansPayeeFinancialAccBIS — IBAN, BIC/branch ID

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnAfterGetPaymentMeansPayeeFinancialAccBIS', '', false, false)]
local procedure OverrideBankAccount(
    SalesHeader: Record "Sales Header";
    var PayeeFinancialAccountID: Text;
    var FinancialInstitutionBranchID: Text)
begin
    if SalesHeader."Currency Code" = 'EUR' then begin
        PayeeFinancialAccountID := 'IS140159260076545510730339';
        FinancialInstitutionBranchID := 'NBIIISREXX';
    end;
end;

Tax

OnAfterGetTaxTotalInfo

Modify the aggregate <cbc:TaxAmount> after it is calculated from the VAT amount lines.

OnAfterGetTaxSubtotalInfo

Modify individual TaxSubtotal fields: taxable amount, tax amount, category ID, percent, scheme ID.

OnGetTotalsOnBeforeInsertVATAmtLine

Intercept each VAT amount line before it is inserted into the aggregation table. Set IsHandled := true to replace the standard insert entirely.

[EventSubscriber(ObjectType::Codeunit, Codeunit::"PEPPOL Management",
    'OnGetTotalsOnBeforeInsertVATAmtLine', '', false, false)]
local procedure OverrideTaxCategory(
    SalesLine: Record "Sales Line";
    var VATAmtLine: Record "VAT Amount Line";
    VATPostingSetup: Record "VAT Posting Setup";
    var IsHandled: Boolean)
begin
    if SalesLine."VAT Bus. Posting Group" = 'EU' then
        VATAmtLine."Tax Category" := 'AE';
end;

OnAfterGetLegalMonetaryInfoWithInvRounding

Modify the LegalMonetaryTotal block after it is calculated, including LineExtensionAmount, TaxExclusiveAmount, TaxInclusiveAmount, AllowanceTotalAmount, PrepaidAmount, PayableRoundingAmount, and PayableAmount.


Coverage summary

XML sectionEvents available
Header fields (Note, TaxPointDate, AccountingCost, InvoiceTypeCode)OnAfterGetGeneralInfo
DueDateOnGetFinalDueDateClaim, OnAfterGetDueDateForInvoice
OrderReferenceOnAfterGetOrderReferenceInfo
BuyerReferenceOnAfterGetBuyerReference
ContractDocumentReferenceOnAfterGetContractDocRefInfo
AdditionalDocumentReferenceOnBefore/AfterGetAdditionalDocRefInfo..., OnBefore/AfterCountAttachments...
Supplier party (identity, address, tax)CourierOnBefore/AfterGetAccountingSupplierPartyInfoByFormat, CourierGetAccountingSupplierPartyPostalAddr, CourierGetAccountingSupplierPartyTaxSchemeBIS, OnAfterGetAccountingSupplierPartyLegalEntityByFormat, OnAfterGetAccountingSupplierPartyContact
Customer party (identity, address, tax)OnAfterGetAccountingCustomerPartyInfoByFormat, OnAfterGetAccountingCustomerPartyLegalEntityByFormat, OnAfterGetAccountingCustomerPartyContact
DeliveryNo
PaymentMeansOnAfterGetPaymentMeansInfo, OnAfterGetPaymentMeansPayeeFinancialAccBIS, OnAfterGetPaymentMeansPayeeFinancialAccBISInternal
TaxTotal / TaxSubtotalOnAfterGetTaxTotalInfo, OnAfterGetTaxSubtotalInfo, OnGetTotalsOnBeforeInsertVATAmtLine
LegalMonetaryTotalOnAfterGetLegalMonetaryInfoWithInvRounding
Line: Note, quantity, amounts, AccountingCostOnAfterGetLineGeneralInfo
Line: Item info, origin countryOnAfterGetLineItemInfo
Line: Price, unit codeOnAfterGetLinePriceInfo
Line: AdditionalItemPropertyOnBeforeAdditionalItemPropertyLoop + AddAdditionalItemPropertyToList()
Full XML blobOnBefore/AfterCreateSalesInvoiceXML, OnBefore/AfterCreateSalesCrMemoXML

For sections without a dedicated event, use OnAfterCreateSalesInvoiceXML / OnAfterCreateSalesCrMemoXML to post-process the full XML blob via DOM manipulation.


Quick reference

XML elementEvent to subscribe
cbc:Note (header)PEPPOL Management · OnAfterGetGeneralInfo
cbc:TaxPointDatePEPPOL Management · OnAfterGetGeneralInfo
cbc:AccountingCost (header)PEPPOL Management · OnAfterGetGeneralInfo
cbc:DueDateXMLport · OnGetFinalDueDateClaim (ClaimDueDate)
Final due date AdditionalDocumentReferenceXMLport · OnGetFinalDueDateClaim (ClaimFinalDueDate)
OrderReference/cbc:IDPEPPOL Management · OnAfterGetOrderReferenceInfo
cbc:BuyerReferencePEPPOL Management · OnAfterGetBuyerReference
ContractDocumentReferencePEPPOL Management · OnAfterGetContractDocRefInfo
AdditionalDocumentReferenceCourier · OnBefore/AfterGetAdditionalDocRefInfo...
Supplier EndpointID / SchemeIDCourier · CourierOnBefore/AfterGetAccountingSupplierPartyInfoByFormat
Supplier postal addressCourier · CourierGetAccountingSupplierPartyPostalAddr
Supplier PartyTaxSchemeCourier · CourierGetAccountingSupplierPartyTaxSchemeBIS
Supplier PartyLegalEntityPEPPOL Management · OnAfterGetAccountingSupplierPartyLegalEntityByFormat
Supplier contactPEPPOL Management · OnAfterGetAccountingSupplierPartyContact
Customer EndpointID / SchemeIDPEPPOL Management · OnAfterGetAccountingCustomerPartyInfoByFormat
Customer PartyLegalEntityPEPPOL Management · OnAfterGetAccountingCustomerPartyLegalEntityByFormat
Customer contactPEPPOL Management · OnAfterGetAccountingCustomerPartyContact
PaymentMeans code / payment IDPEPPOL Management · OnAfterGetPaymentMeansInfo
Bank account (IBAN / BIC)Courier · OnAfterGetPaymentMeansPayeeFinancialAccBISInternal
TaxTotal/cbc:TaxAmountPEPPOL Management · OnAfterGetTaxTotalInfo
TaxSubtotal fieldsPEPPOL Management · OnAfterGetTaxSubtotalInfo
Tax category per linePEPPOL Management · OnGetTotalsOnBeforeInsertVATAmtLine
LegalMonetaryTotalPEPPOL Management · OnAfterGetLegalMonetaryInfoWithInvRounding
InvoiceLine/cbc:NotePEPPOL Management · OnAfterGetLineGeneralInfo (InvoiceLineNote)
InvoiceLine/cbc:AccountingCostPEPPOL Management · OnAfterGetLineGeneralInfo (InvoiceLineAccountingCost)
Line quantity / extension amountPEPPOL Management · OnAfterGetLineGeneralInfo
Item Name / DescriptionPEPPOL Management · OnAfterGetLineItemInfo
Item origin countryPEPPOL Management · OnAfterGetLineItemInfo
Unit price / unit codePEPPOL Management · OnAfterGetLinePriceInfo
AdditionalItemProperty per lineXMLport · OnBeforeAdditionalItemPropertyLoop + AddAdditionalItemPropertyToList()
Full XML blobCourier · OnBefore/AfterCreateSalesInvoiceXML / OnBefore/AfterCreateSalesCrMemoXML