This guide provides step-by-step instructions for installing and configuring the Peppol Invoicing managed package for Salesforce. This package enables you to send Peppol-compliant invoices directly from Salesforce, leveraging the power of PDF Butler.
1.In Salesforce Setup, use the Quick Find box to search for Permission Sets.
2. Find and click the PEPPOL Butler Admin permission set.
3. Click the Manage Assignments button.
4. Assign this permission set to the API user (or administrative user) responsible for the registration and Peppol API processing.
Navigate to the Data Sources related list on the Doc Config record you just created.
Create the Data Sources needed to generate your PDF document from your Invoice record (or other Salesforce object). In this example, we create 2 Data Sources to retrieve custom Salesforce ObjectInvoice record with the child Invoice Lines records:
Create the First Data Source Record for your Invoice obect and populate the fields:
.Name: i.e. Peppol Invoice.
.Record Type: SOQL – because we need to retrieve Invoice__c data from Salesforce.
.Type: Single Object.
.SOQL: paste query to retrieve Invoice with the related data from other objects we want to show on the PDF document: Account, Opportunity, Contract, etc., for example:
SELECT Id, Invoice_Number__c, Invoicing_Date__c, Invoice_Amount__c,
Account.BillingCountry, Account.BillingStreet, Account.BillingPostalCode, Account.BillingState, Opportunity__r.Billing_period__c, Opportunity__r.Foreign_Curr_Total_With_VAT__c,
Contract__r.Invoice_Payment_Term__c
FROM Invoice__c
WHERE Id = :recordId
Hint : Easily to build your query use SOQL BUILDER button on the Data Source record to open the SOQL builder.
Create a child Data Source record for the multiple Invoice Lines records:
Name: Invoice Lines.
Record Type: SOQL.
Type. List of sObjects.
SOQL.
SELECT Name, Product_Description__c, Quantity__c, Invoice__c, Unit_Price__c, Total_Price__c, CurrencyIsoCode FROM Invoice_Line_Item__c
FROM Invoice_Line_Item__c
WHERE WHERE Invoice__c = :recordId
3. On the Doc Config record page, find the Custom Links section and click Open PDF Butler.
4. Configure your document template with the PDF Butler web App UI. Upload here your document template and map your Salesforce Data Sources Object fields with the template placeholders:
Assign ‘Peppol’ page latyout for the Actionable.Peppol Record Type. So, Admins can see all the Peppol fields on the Actionable when set Peppol record type:
a. Go to Setup -> Object Manager and Open Actionable object (cadmus_core__Actionable__c) -> Page Layouts and click ‘Page Layout Assignments’ button in the top right corner of the page.
b. Locate ‘Peppol’ record type. Then click ‘Edit Asignment’ and change a default to ‘Peppol’ page layout. Save changes.
Navigate to the Actionables related list on the Pack record you just created.
Click New to create a new Actionable record:
Name: Enter a descriptive name (e.g., Send Peppol Invoice)
Record Type:Peppol (This record type is added by the Peppol Invoicing package) – Should be preselected.
Class Name: cadmus_peppol.Peppol_ProcessInvoice
When (picklist): After Generation.
Active: checked (true)
Flow API Name: Enter the API name of the custom Flow you will configure in the next section (e.g., Peppol_InvoiceActionable). This Flow is called by the actionable and is required to map your Salesforce Invoice data into the Peppol Invoice Payload Object.
Add the PDF Butler component to your Invoice record page to allow users to generate and send the invoice.
Navigate to one of your Invoice records (or another Salesforce object page you are using, like Opportunity).
Click the Setup Gear Icon (⚙️) in the top-right corner and select Edit Page.
From the component list on the left, find and drag the PDF Butler Previewer component onto your page layout.
In the component’s properties panel on the right, configure the following:
Doc Config Id: Enter the Salesforce ID of your Doc Config record (from step 4.1).
Pack Id: Enter the Salesforce ID of your PDF Butler Pack record (from step 5.1).
Hint: You can get these IDs by navigating to the records in Salesforce and copying the 18-character ID from the URL (e.g., …/a0A8d00000A…).
Save and Activate the page.
Check the Configuration. Locate ‘PDF Butler Document Previewer’ on the Record page. Click ‘PDF’ button. If everything was done correctly you should see your document preview open in the modal window:
Locate the ‘Send Invoice’ button. As this component is associated with the Doc Config and the Pack (configured in step 5 above), it will invoke the Actionable class with the Flow you defined for the ‘Flow API Name’ field (e.g., Peppol_InvoiceActionable).
In the next step below, will see how to configure the Flow step by step.
The Autolaunched Flow is the most critical part of the setup. It maps your Salesforce data to the Peppol_Invoice object, which is then sent to the Peppol API.
7.2. Create recordId Input Variable to get Salesforce Record Id: #
When a user clicks “Send Invoice” in the PDF Butler Previewer, the actionable passes the current Record ID into this Flow, therefore we need to create a variable recordId to store this input value. Later we can use the variable to get all the required Invoice fields and related records data.
In the Flow’s Toolbox, click New Resource and select Resource Type Variable.
Create a new Variable with the following properties:
API Name: recordId (This must be the exact name)
Data Type: Text
Available for Input: Checked (true) – because we get the record id from the record page – from outside of the Flow:
3. Click ‘Save’ button in the right top corner. Enter Flow Label and Flow API Name, optionally Description, and click Save.
The Peppol Invoicing package includes Peppol Invoice (Peppol_Invoice) object that mirrors the Peppol Invoice API Payload (JSON) structure. So we create a new Apex-Defined variable with the same name Peppol_Invoice:
To map Salesforce data from Invoice and related records.
Send it with the Apex Action to the Peppol Web Service for an outbound request to Peppol Network.
So, let’s create the variable:
1. In the Flow’s Toolbox, click New Resource.
2. Create a new Variable with these properties:
API Name: Peppol_Invoice (or a name of your choice)
Next key element, is the Flow Action that will connect our flow with the Peppol Service class.
Click on the + sign to add Action to the end of your Flow:
2. Search for and select the action apex class: cadmus_peppol__Peppol_ServiceFlow:
3. Set the two required Input Values for the Action – the variables we have created in previous steps:
.recordId: Pass in your recordId variable (step 7.2).
.cadmus_peppol__Peppol_Invoice (step 7.3).
4. Save your Flow. At this point our flow is ready for testing and validating the payload.
· In the beggining the autolunched flow is invoiked by the Actionable (cadmus_peppol.Peppol_ProcessInvoice).
· On the other side it is passing Peppol Invoice object to the Action (Peppol_ServiceFlow). Finally, we need to add a step in between – get Salesforce data for Peppol_Invoice object.
In this step we need to get Salesforce Data in order to map it to Peppol Invoice Object. Here you basically need to replicate the queries you have done for the Data Sources queries in step 4 above.
Add a Get Records element to the Flow.
Use this element to retrieve your Salesforce Invoice record (and any related records, like Products or Contacts) using the recordId variable from the previous step.
Get Invoices:
Hint: Next to the *Object field tick Also get related records (beta) checkbox – to retrieve related to Invoice object fields like: Account, Opportunity fields.
Get Invoice Lines:
Click Save, to save these changes in the Flow.
7.6. Map Salesforce Data to Peppol Invoice Object in the Flow #
In the previous step 7.5 we have retrieved data from Salesforce, so now we can map it to Peppol Invoice object defined by the Peppol_Invoice variable created in step 7.3. Note, this variable represents a complex structure with the nested objects and collections items we have to populate with Salesforce data:
Invoice (cadmus_peppol__Peppol_Invoice) – top level required object.
Lines (Peppol_InvoiceLine) – required collection of objects.
Additional Properties (Properties Peppol_AdditionalProperty) – collection of objects (optional).
Vat Totals (Peppol_InvoiceVatTotals) – required collection of objects.
Payment Methods (Peppol_InvoicePaymentMethod) – collection of objects.
Attachments (Peppol_InvoiceAttachment) – collection of objects. Not required to map in the Flow because PDF Attachments will be generated by the PDF Butler Previewer component and handled automatically to the Peppol API.
To understand more about these objects purpose refer to Apendix 1: Invoice Object and Fields.
Now, let us see how map at least the required objects in the Flow step by step:
1. Invoice. On the Flow canvas click plus to add a new Flow Assignment item and give it a name, i.e. “Invoice”. To see all the Invoice object properties refer to Invoice (Peppol_Invoice) table.
Note, that some of the fields like INVOICE_NUMBER is marked as required in the table Top-Level Invoice Fields. These are mandatory fields to create an Invoice sucessfully on the Peppol network, otherwise you will get Peppol HTTP error responses.
INVOICE_NUMBER. It shoud be a unique number or autonumber for your Invoice record. On the Assingment under the Set Variable Values -> Variable – select Peppol_Invoice variable and then select INVOICE_NUMBER property:
Next, let’s to assign a value using Equals operator from the Get Records component (step 7.5 above):
Variable – {!Peppol_Invoice.INVOICE_NUMBER}
Operator – Equals
Value – {!Get_Invoice.Invoice_Number__c}. We get this values from the Get Invoice element, that in our data model example gets Invoice.Invoice_Number__c field value.
Well done! Let’s click + Add Assignment button below to map other Peppol_Invoice top-lever required properties:
Hint: Above we provided an example how to reference Salesforce fields using Get Records. Obviously, your data model can be different. So, further we will use the static values for simplicity:
INVOICE_TOTAL_VAT. Other properties can be mapped from the Invocie or related records query result
{!Peppol_Invoice.INVOICE_TOTAL_VAT } Equals 21
IS_INCLUDE_VAT – default value is false. This field is not required, but it controlls other object VAT properties become required: Include VAT or Exclude VAT for all the Invoice objects. In our case let’s explicetly set it to FALSE (flow globar variable):
{!Peppol_Invoice.IS_INCLUDE_VAT} Equals FALSE
Add new assignment for INVOICE_TOTAL_EXCL_VAT (required if IS_INCLUDE_VAT = false):
Well done! Click ‘Save’ button on the top to save your in the Flow.
Hint: Now, you can debug the Flow to validate your Data mapping for the required properties:
Click Debug button on the Flow page.
On the Input Variables block populate recordId with the your Invoice id you can copy from the browser url (see step 6 for details).
Click Run button at the bottom of the Flow page and wait untill the Flow execution completes.
Expand Assignment: Invoice block to check the results if all the properties were populated from the corresponding Salesforce fields:
Finally, click > Details to expand the Apex Action block to see the validation results:
Invoice (Peppol_Invoice) – you can see your data mapping in JSON format.
Validation Result (under the Outputs) – that Peppol_Invoice.SUPPLIER object is required for the Invoice, therefore it’s now enough to provide just the top-level invoice data.
Also you shoud see similar validation error when removeing any of the required Invoice fields that we mapped above, for example INVOICE_ISSUE_DATE. So, debuggin Flow is useful to validate your logic for every further step below before sending it to the Peppol Network.
Next, let us populate data for the Peppol_Invoice.SUPPLIER.
2. Supplier. Let us populate some of the required fields for the SUPPLIER object. To add other Supplier fields to the payload refer to Supplier (Peppol_InvoiceSupplier) table.
Click + after the Invoice Assignment element to add a Supplier block:
SENDER_PEPPOL_ID (required):
Identifies the supplier in the Peppol network.
Must be registered through an Access Point (e.g., Scrada).
Used in the invoice header so the receiver knows who sent it.
Example: iso6523-actorid-upis:: 9915:test-sender
{!Peppol_Invoice.SUPPLIER.SENDER_PEPPOL_ID} Equals 9915:test-sender – this value can be stored on a Sender Account in Salesforce.
SENDER_NAME:
{!Peppol_Invoice.SUPPLIER.SENDER_NAME} Equals Test Sender– Sender Account name, for example “Sender Account”.
SENDER_VAT:
{!Peppol_Invoice.SUPPLIER.SENDER_VAT} Equals BE0676494529– Sender Account name, for example “Sender Account”.
SENDER_COUNTRY. Let’s also populate other address fields (not required) from the Sender’s Account.BillingAddress:
{!Peppol_Invoice.SUPPLIER.SENDER_COUNTRY} Equals {!GetRelatedObjects.Account__r.BillingCountry} // BE
3. Customer. On the Flow page click + to add another Assignment block for the Customer object. To add other Customer fields to the payload refer to Customer (Peppol_InvoiceCustomer) table.
RECIPIENT_PEPPOL_ID. The PeppolID of the receiver (the company receiving the invoice).
Identifies the customer as a Peppol participant.
Required to deliver the invoice through the Peppol network.
Must also be registered with an Access Point somewhere in Peppol.
Example: iso6523-actorid-upis::0088:123456789
{!Peppol_Invoice.CUSTOMER.RECIPIENT_PEPPOL_ID} Equals Account__r.CustomerPeppolId__c – the test value 0088:987654321 can be also set on a Customer’s Account in Salesforce.
RECIPIENT_NAME:
{!Peppol_Invoice. CUSTOMER.RECIPIENT_NAME} Equals Account__r.Name– Sender Account name, for example “Sender Account”.
RECIPIENT_COUNTR. Let’s also populate other address fields (not required) from the Sender’s Account.BillingAddress:
4. VAT Totals. Add another Assignment block for the Vat Totals. See, all the detailed info about Vat Totals properties in the VAT Totals (Peppol_InvoiceVatTotals) table.
Vat totals can have multiple items (collection) each of them we need to store into an Apex-Defined variable, let’s call it “VatTotal” that represents the Peppol_InvoiceVatTotals object:
Click + on the Flow line (right after Customer assignmet) to add a new Assignmet bock. Let’s call it VAT Totals. Then let’s add assignments using the VatTotals variable like we did before and to assign sample numbers:
As result we have just created two assignments for Vat Totals:
Hint: Instead of adding a single item we could us the Flow Loop component to add multiple values into Peppol_Invoice.INVOICE_VAT_TOTALS collection. Let’s demonstrate it in next step for the Invoice Line Items.
5. Invoice Lines. (Peppol_InvoiceLine) – collection. See, all the detailed info about Vat Totals properties in the Invoice Line (Peppol_InvoiceLine) table.
In this sample Salesforce data model: Invoice (parent) has Invoice Lines (children) records. Refer to step 7.5 where we used Get Invoice Lines (Get Records) we can Loop though all the Line Items with the Flow Loop component. Click + to add the Loop component and reference Get Invoice Lines under the collection variable:
Let us create an Apex-Defined variable to represent Peppol_InvoiceLine object to store a single Line Item and call it InvLineItem:
Inside the Loop block click the + icon to create an Assignment component to map the Line Item values to the InvLineItem variable:
When you successfully send an invoice, a Peppol Sales Invoice record is automatically created. This object tracks the status of the submission.
Invoice Status: This field tracks the two-step callback process:
Created: This is the first callback, indicating the invoice was successfully pushed to the Peppol network.
Processed: This is the second callback, indicating the invoice was fully processed by Peppol and a UBL document has been generated. At this stage, the UBL document will be attached to the Files related list of this PeppolSalesInvoice__c record.
SF_RecordId__c (cadmus_peppol__SF_RecordId__c): This field stores the Salesforce Record ID of the object you sent (e.g., your custom Invoice record). You can use this field in a Flow or Trigger to relate the PeppolSalesInvoice__c record back to your original Salesforce record.
Sample Query:
SELECT Id, Name, cadmus_peppol__Status__c, cadmus_peppol__Status_Message__c, cadmus_peppol__Invoice_Number__c
FROM cadmus_peppol__PeppolSalesInvoice__c
WHERE CreatedDate = TODAY
ORDER BY CreatedDate DESC
Peppol Sales Invoice Name – is an autonumber field. Populated automatically when record created.
Invoice Number – stores Peppol Invoice Id from the Peppol response.
Status – picklist that mirrors Peppol Invoice Status, that corresponds the corresponding HTTP status code.
'Created' -> 201, // Created – passed the validation
'Processed' -> 202, // Accepted by Peppol, UBL file generated
'Error' -> 500, // Internal Server Error
'Error already sent' -> 409, // Conflict
'Error not on Peppol' -> 422 // Unprocessable Entity – declined by Peppol
Status Message – even if the logs not enabled (step 8.2 below), the Status Message is populated and explains the Invoice Status.
Notes & Attachments – stores the Invoice UBL (XML) file generated by Peppol when Invoice status updated to Processing.
Peppol Log is a Custom Object included into Peppol Invoicing package. Peppol Loggin can be enabled or removed from the Peppol Admin tab -> Configuration Section:
Click Switch On Logging (for 1 hour only) button, so the logs will be generated during a next hour for your testing.
Extend Logging button – will extend the loging time for 1 hour from a current time.
Remove logging – will clear the logging time, so the logs will not be generated anymore.
If the logging is enabled the log records will be created for the both successful and failed transactions when sending Invoices to Peppol Network or getting a response for an Invoice status update:
API Name: Peppol Log object (cadmus_peppol__PeppolLog__c)
Sample Query:
SELECT Id, cadmus_peppol__Endpoint__c, cadmus_peppol__LogMessage__c,
cadmus_peppol__ResponseBody__c, CreatedDate
FROM cadmus_peppol__PeppolLog__c
WHERE CreatedDate = TODAY
ORDER BY CreatedDate DESC
Peppol Log Name – an autonumber field, generated automatically when record created.
Endpoint – HTTP request partial enpoint.
Action – describes the Log origin.
Method – HTTP request method name.
Status Code – HTTP request status code.
Payload – HTTP request payload.
Log Message – provides extra details about the request success or error processing.
1. Peppol Invoice (cadmus_peppol__Peppol_Invoice) – top level object
This is the root container for the invoice being sent over the Peppol network. It holds all the main invoice data — such as who is sending the invoice, who is receiving it, the delivery information, the detailed lines, taxes, payment methods, and any attachments. Structuring the invoice in this object ensures everything needed for Peppol / UBL compliance is included in one cohesive payload.
2. Supplier (Peppol_InvoiceSupplier) – object
This represents the party issuing (sending) the invoice — i.e. your company (the seller). It includes identifiers needed for Peppol routing, contact information, VAT / tax numbers, and any “extra identifiers” that may be required to uniquely identify the supplier on Peppol. This ensures the recipient (customer) and the Peppol network know exactly who the invoice is coming from and that it is legally valid.
3. Extra Identifier (Peppol_InvoiceExtraIdentifier) – collection under Supplier
This is a list of additional identifiers for your company (supplier) beyond the primary ones (like VAT number). In Peppol / UBL, a party can have multiple identifiers (for example, GLN, tax number, or other scheme). These extra identifiers make sure your supplier identity is correctly recognized, especially in cases where the customer or Peppol requires a specific identifier scheme.
4. Customer (Peppol_InvoiceCustomer) – object
This describes the party receiving the invoice — i.e. your customer (the buyer). It includes their Peppol ID, business identifiers (VAT, GLN, etc.), contact info, and address. This information is critical for routing the invoice correctly through the Peppol network and ensuring the invoice is legally addressed to the correct business entity.
5. Delivery (Peppol_InvoiceDelivery) – object
This captures details about where (and when) goods or services were delivered (or will be delivered), if applicable. It contains a delivery date, the delivery address, and an identifier (with type) for the delivery location. This is important in many use cases (especially goods) to link the invoice to the actual delivery, which is often required in formal invoicing / UBL documents.
This is a list of the individual items, services, or billing lines on the invoice. Each line holds detailed information (e.g. description, quantity, unit price, discounts, VAT) so that the invoice can be itemized in a way that is transparent, auditable, and consistent with UBL / Peppol requirements. This level of detail is necessary for accurate calculation of totals, tax, and for the buyer to understand exactly what they are being billed for.
This represents the summary of VAT amounts broken down (e.g. by VAT rate). Instead of just having a gross VAT amount, these summaries ensure that the invoice clearly states how much VAT is charged per rate, and how it contributes to the invoice total. This is essential for compliance and for accounting purposes for both parties, as well as for proper UBL / Peppol formatting and validation.
This defines how the customer is expected to pay the invoice (or what payment methods are acceptable). For example, bank transfer, credit terms, or other methods. By explicitly listing acceptable payment methods, you communicate payment instructions to the customer in a
structured way, and help ensure that payment terms are clear and machine-readable, which is especially useful in automated systems or accounting software.
These are any files or additional documents attached to the invoice, such as PDF versions, supporting documentation, or evidence (e.g., delivery notes or signed receipts). Including attachments ensures that everything needed to support or validate the invoice is bundled and can travel with the invoice over Peppol, making it easier for the customer to review and for automated systems to archive or reconcile the invoice.
1: Standard rate (If line is 0% VAT then Zero rate must be used. Standard rate cannot be used even if it is the standard rate for this product/service) (Belgium: VAT Box 01, 02 or 03)
2: Zero rate (Belgium: VAT Box 00)
3: Exempt from tax (Diversen na BTW/Divers hors TVA) (Belgium: Not on VAT Declaration)
The item price excluding VAT, required when IS_INCLUDE_VAT is set to false. Default is 0. Max precision is 4. See, the Invoice table above: IS_INCLUDE_VAT
LI_PRICE_INCL_VAT
Number or null <double>
Yes/No
The item price including VAT, required when IS_INCLUDE_VAT is set to true. Default is 0. Max precision is 4. See, the Invoice table above: IS_INCLUDE_VAT
1: Standard rate (If line is 0% VAT then Zero rate must be used. Standard rate cannot be used even if it is the standard rate for this product/service) (Belgium: VAT Box 01, 02 or 03)
2: Zero rate (Belgium: VAT Box 00)
3: Exempt from tax (Diversen na BTW/Divers hors TVA) (Belgium: Not on VAT Declaration)
53: Standard exchange (Standaardruil/Echange standard) (Belgium: VAT Box 03)
54: Margin (Marge/Marge)
70: OSS Goods
71: OSS Services
72: OSS Import
LI_VAT_PERCENTAGE
Number <double>
Yes
The VAT percentage. Can be 0% for VAT excluded items. Default is 0. Max precision is 2.
LI_TOTAL_DISCOUND_EXCL_VAT
Number or null <double>
No
The total discount for the entire line (not per product) excluding VAT, can be used when IS_INCLUDE_VAT is set to false. Default is 0. Max precision is 2. See, the Invoice table above: IS_INCLUDE_VAT
LI_TOTAL_DISCOUND_INCL_VAT
Number or null <double>
No
The total discount for the entire line (not per product) including VAT, can be used when IS_INCLUDE_VAT is set to true. Default is 0. Max precision is 2. See, the Invoice table above: IS_INCLUDE_VAT
LI_TOTAL_EXCL_VAT
Number or null <double>
Yes/No
The total line price excluding VAT, should be equal to (quantity * itemExclVat) – LI_TOTAL_DISCOUND_EXCL_VAT, required when IS_INCLUDE_VAT is set to false. Default is 0. Max precision is 2.
See, the Invoice table above: IS_INCLUDE_VAT
LI_TOTAL_INCL_VAT
Number or null <double>
Yes/No
The total line price excluding VAT, should be equal to (quantity * itemInclVat) – LI_TOTAL_DISCOUND_INCL_VAT, required when IS_INCLUDE_VAT is set to true. Default is 0. Max precision is 2.
LI_INVOICE_PERIOD_START_DATE
String or null <date>
No
The start date of the invoice line period.
LI_INVOICE_PERIOD_END_DATE
String or null <date>
No
The end date of the invoice line period.
LI_STANDARD_ITEM_IDENTIFIER_TYPE
Integer
Enum: 1 2 3 20 21 22
No
Specifies the type of identification number used to uniquely identify a company, organization, or item.
1: Numero d’entreprise / ondernemingsnummer / Unternehmensnummer / Enterprise number (Belgium)
2: Kamer van koophandel nummer (the Netherlands)
3: SIRENE (France)
20: Global Location Number [GLN] (must be 13 digits)
21: Global Trade Item Number [GTIN] (must be 8, 12, 13, or 14 digits)
22: GS1 identification key (must be between 8 and 20 digits) global Integer LI_STANDARD_ITEM_IDENTIFIER_TYPE = null;
LI_STANDARD_ITEM_IDENTIFIER
String or null
No
The standard item identifier. Required when standardItemIdentifierType is provided.
LI_PURCHASE_ORDER_LINE_REFERENCE
String or null
No
The purchase order line reference. Requires purchaseOrderReference on the invoice to be filled in.
A URI pointing to an external document resource. If this field is provided, the fields filename, mimeType, base64Data and note should remain null. fileType remains required. Use this when the document is hosted externally instead of being uploaded.