If you have built an invoicing tool, a marketplace, or a SaaS product on Bubble and you serve customers in Belgium, the Netherlands, the Nordics, or anywhere on the Peppol network, structured UBL invoicing is now a requirement. Belgium's B2B mandate went live in January 2026, meaning every VAT-registered business there must send and receive Peppol BIS 3.0 invoices. The Netherlands, Norway, Denmark, Singapore, Australia, and Japan all run on the same network.
The problem is that Bubble has no native way to produce or check these documents. Peppol UBL is not a PDF and not freeform XML. It is a precise structure following the EN 16931 standard, layered with Peppol's own rules, and validated against more than 250 business rules in total. None of that is something Bubble or a generic plugin can do.
This guide shows how to generate and validate fully compliant Peppol UBL invoices directly from Bubble using the InvoiceXML REST API, with no plugins and no server-side code. The create step is a single JSON call that returns ready-to-send UBL.
Peppol and UBL in plain terms
If this is new, here is what matters for a Bubble build.
UBL (Universal Business Language) is an XML format for business documents. For e-invoicing across Europe and beyond, the relevant flavour is Peppol BIS Billing 3.0, which is UBL shaped to the EN 16931 European standard plus a set of Peppol-specific rules. Peppol itself is the network that delivers these invoices between businesses, a bit like an email network but for structured invoices.
Unlike ZUGFeRD or Factur-X, Peppol UBL is pure XML with no PDF wrapper. A machine reads it perfectly, but a human cannot, which is why rendering a readable PDF copy is a common companion step (covered below).
The hard parts, and the reasons you cannot do this natively in Bubble, are generating valid EN 16931 UBL, applying the correct national profile (Belgium, Netherlands, Norway, and others each have their own variant), and running the two layers of Schematron validation that decide whether the Peppol network actually accepts the invoice. Schematron requires XSLT 2.0 processing that no Bubble plugin provides. InvoiceXML handles all of it.
What you need
A free InvoiceXML account. Sign up for 100 free credits, no credit card required. Copy your API key from the dashboard.
The Bubble API Connector plugin, which is free and built by Bubble. Add it from the Plugins tab if it is not already installed.
That is the entire stack.
Create Peppol UBL from invoice data
This is the main flow and it is genuinely simple, because UBL is text. You send invoice data as JSON and receive ready-to-send UBL 2.1 XML, already validated against EN 16931 and the Peppol overlay.
Set up the API call
In the API Connector, add a new API named "InvoiceXML" and create a call:
- Name:
Create UBL - Use as: Action
- Method: POST
- URL:
https://api.invoicexml.com/v1/create/ubl - Body type: JSON
- Add a header:
Authorizationwith valueBearer YOUR_API_KEY
In the body, paste a JSON template. The <> markers tell Bubble which values are dynamic:
{
"invoice": {
"invoiceNumber": "<invoiceNumber>",
"issueDate": "<issueDate>",
"currency": "EUR",
"seller": {
"name": "<sellerName>",
"vatIdentifier": "<sellerVat>",
"postalAddress": {
"line1": "<sellerStreet>",
"city": "<sellerCity>",
"postCode": "<sellerPostCode>",
"country": "BE"
}
},
"buyer": {
"name": "<buyerName>",
"postalAddress": {
"line1": "<buyerStreet>",
"city": "<buyerCity>",
"postCode": "<buyerPostCode>",
"country": "BE"
}
},
"paymentDetails": {
"paymentAccountIdentifier": "<iban>"
},
"lines": [
{
"quantity": "<quantity>",
"priceDetails": { "netPrice": "<netPrice>" },
"vatInformation": { "rate": 21 },
"item": { "name": "<itemName>" }
}
]
},
"options": {
"profile": "peppol-bis-3"
}
}
The response is UBL XML as text, so you can set the call to return text and store the result in a Bubble text field. Initialize, then switch the parameters to dynamic.
The profile parameter picks the market
The options.profile value controls which national Peppol variant the invoice follows. This is the single most important setting for serving multiple countries:
profile value | Use case |
|---|---|
peppol-bis-3 | Belgium, Denmark, Sweden, cross-border EU (default) |
nlcius | Netherlands |
ehf | Norway |
xrechnung | Germany public sector (UBL syntax) |
pint | Peppol PINT base (Singapore, Australia, Japan, Malaysia, New Zealand) |
en16931 | Plain EN 16931, no national overlay |
If your Bubble app serves customers in several countries, you set this field dynamically per invoice and the correct rules apply automatically.
Multiple line items
The lines field is an array, so you add more invoice lines by including more objects:
"lines": [
{
"quantity": 100,
"priceDetails": { "netPrice": 50.00 },
"vatInformation": { "rate": 21 },
"item": { "name": "Software development services" }
},
{
"quantity": 5,
"priceDetails": { "netPrice": 120.00 },
"vatInformation": { "rate": 21 },
"item": { "name": "Onboarding session" }
}
]
Totals and the VAT breakdown are calculated automatically across all lines. If your line items live in a repeating group or a list, you build this array dynamically (more on that below).
Validation is built in
A key point: the create endpoint validates the invoice against EN 16931 and the Peppol overlay before returning it. If the data is incomplete or inconsistent, you get a clear error describing what is wrong rather than an invalid file. So for invoices you generate yourself, you do not need a separate validation step. The XML you receive is already network-ready.
Validate incoming UBL from suppliers
When your users receive UBL invoices from their suppliers (uploaded into your app), you can check them against the full Peppol ruleset before importing. This is where a file upload is involved, and the file is already in Bubble because the user uploaded it.
Set up a second call:
- Name:
Validate UBL - Method: POST
- URL:
https://api.invoicexml.com/v1/validate/ubl - Body type: Form-data
- Header:
Authorizationwith valueBearer YOUR_API_KEY - Add one parameter named
file, and check the box "This parameter is a file."
That checkbox is essential. Without it, Bubble sends the file's URL as text and the validation receives nothing usable. To initialize, upload a sample UBL file in the dialog, then switch the parameter to dynamic and map it to your File Uploader's value.
The response reports whether the invoice is valid and lists any rule violations in plain language, distinguishing data errors (EN 16931) from Peppol network errors. You can show these to your users so they know what to fix or send back to the supplier. The exact response structure is in the docs.
One Bubble tip: the API Connector treats some HTTP responses as errors by default. Use the "Include errors in response and check for them" option on the call so you can read validation findings rather than have Bubble treat an invalid invoice as a hard error.
Render UBL as a human-readable PDF
Because UBL is pure XML, neither your users nor their customers can read it directly. For approval screens, email attachments, or record-keeping, render a clean PDF from the UBL. Point the call at a UBL file stored in Bubble:
- Name:
Render UBL to PDF - Method: POST
- URL:
https://api.invoicexml.com/v1/render/ubl/to/pdf - Body type: Form-data
- Header:
Authorizationwith valueBearer YOUR_API_KEY - Add a
fileparameter with "This parameter is a file" checked - Set the response type to file (it returns a PDF)
The rendered PDF shows the detected Peppol profile, all line items, the VAT breakdown, and payment details. It is for human review only and has no legal standing, the UBL XML remains the authoritative invoice, but it is exactly what an approver or a customer wants to look at.
If you generated the UBL with the create call and have it as text, save it to Bubble storage first (which gives it a file and a URL), then pass that file to this call.
Building the line items array dynamically in Bubble
The one part that trips up Bubble builders is constructing the lines array from a list of items (a repeating group, or a list field on your invoice). Bubble does not build JSON arrays natively in the API Connector body, so use one of these approaches.
The cleaner option is a backend (API) workflow using Bubble's :format as text operator on your list of line items. You define the per-line JSON shape once, and Bubble repeats it for each item in the list, joining them with commas to form the array. This keeps everything inside Bubble and, by running server-side, keeps your API key off the client, which is the right practice for any production app since the Authorization token should never be exposed in the browser.
If you would like the exact format as text expression for the line items array, contact us and we will send a working example for your setup.
Why this works when native Bubble cannot
To be clear about what InvoiceXML is doing so you can trust the output:
It generates valid EN 16931 UBL with the correct CustomizationID and the structure the Peppol network expects. Hand-building UBL XML in Bubble is error-prone and a single wrong element causes rejection.
It runs both Schematron layers, the EN 16931 base rules and the Peppol BIS 3.0 overlay, which together decide whether the network accepts the invoice. This requires XSLT 2.0 processing that no Bubble plugin provides. The Peppol layer also updates quarterly, and those updates are deployed for you with no change to your app.
It applies the right national profile automatically from the profile parameter, so serving Belgium, the Netherlands, and Norway is a field change rather than three separate implementations.
It is stateless. Your invoice data is processed in memory and purged immediately on response. Nothing is stored, nothing is logged, no data is used for training. GDPR compliant by architecture, which matters when handling your customers' financial documents.
Endpoint summary
| Operation | Endpoint | Body | Returns |
|---|---|---|---|
| Create Peppol UBL | POST /v1/create/ubl | JSON (with options.profile) | UBL 2.1 XML |
| Validate UBL | POST /v1/validate/ubl | Form-data (file) | Validation result |
| Render UBL to PDF | POST /v1/render/ubl/to/pdf | Form-data (file) | PDF file |
Full docs and interactive explorer: invoicexml.com/docs
Get started
Create a free InvoiceXML account →, grab your API key, and you can have compliant Peppol UBL generating from your Bubble app in under an hour.
If you get stuck on the Bubble integration, reach out. We have helped Bubble teams wire this up and are happy to send a working example for your exact workflow.
Related resources:
- Create and validate Factur-X & ZUGFeRD invoices on Bubble
- UBL API: the complete Peppol toolkit (BIS 3.0, profiles, and the CustomizationID)
- E-invoicing mandate deadlines by country
- Full API documentation
InvoiceXML is a REST API for European e-invoice compliance covering Peppol UBL, CII, ZUGFeRD, Factur-X, and XRechnung. Build compliant e-invoicing into any no-code or low-code platform, including Bubble, with a single API call. Stateless and GDPR compliant by architecture.