Automation MCP Server Features Blog Pricing Contact
Automation

Create and Validate Peppol UBL Invoices on Bubble (No Code)

Bubble has no native way to produce or check Peppol UBL invoices. This guide shows how to generate, validate, and render fully compliant Peppol BIS 3.0 UBL directly from your Bubble app with a single API call: no plugins and no XSLT, ready for Belgium's 2026 mandate and the wider Peppol network.

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: Authorization with value Bearer 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 valueUse case
peppol-bis-3Belgium, Denmark, Sweden, cross-border EU (default)
nlciusNetherlands
ehfNorway
xrechnungGermany public sector (UBL syntax)
pintPeppol PINT base (Singapore, Australia, Japan, Malaysia, New Zealand)
en16931Plain 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: Authorization with value Bearer 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: Authorization with value Bearer YOUR_API_KEY
  • Add a file parameter 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

OperationEndpointBodyReturns
Create Peppol UBLPOST /v1/create/ublJSON (with options.profile)UBL 2.1 XML
Validate UBLPOST /v1/validate/ublForm-data (file)Validation result
Render UBL to PDFPOST /v1/render/ubl/to/pdfForm-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:


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.

Start free today

Ready to automate your invoices?

Validate, convert and embed compliant e-invoices through one API. Start your 30-day free trial. No credit card required.

GDPR Compliant No credit card required Setup in minutes
Peppol UBL
Factur-X
EN 16931
142 / 142 passed
Compliant
PDF/A-3 embedded