If you have built an invoicing tool, a marketplace, or a SaaS product on Bubble and you serve customers in Germany or France, e-invoicing compliance is coming for you. Germany's B2B mandate requires structured invoices, with the issuing obligation phasing in from January 2027. France's mandate begins September 2026. Both require ZUGFeRD or Factur-X, which are not just PDFs and not just XML, but a specific hybrid of the two.
The challenge is that Bubble has no native way to produce these files. They require PDF/A-3b container generation, embedded XML following the EN 16931 standard, and validation against more than 200 business rules. None of that is something you can build with Bubble's native features or a generic PDF plugin.
This guide shows how to generate and validate fully compliant Factur-X and ZUGFeRD invoices directly from Bubble using the InvoiceXML REST API, with no plugins, no server-side code, and crucially no file-upload headaches. Everything happens through a single API call that takes JSON and returns your finished invoice.
Factur-X and ZUGFeRD in plain terms
If the formats are new to you, here is what matters for a Bubble build.
ZUGFeRD and Factur-X are the same thing under two names. ZUGFeRD is the German brand, Factur-X is the French brand, and the current versions are technically identical (ZUGFeRD 2.4 equals Factur-X 1.08). Both are a PDF/A-3b file (a long-term archival PDF) with a structured XML invoice embedded inside it. A human opens the PDF and sees a normal invoice. A machine reads the embedded XML and gets every field as structured data. One file serves both purposes, which is why these formats won in the Franco-German market.
The hard parts, and the reasons you cannot do this natively in Bubble, are the PDF/A-3b conformance (specific fonts, colour profiles, and metadata), the EN 16931 XML generation, and the Schematron validation (the 200+ business rules that determine whether the invoice is actually accepted). InvoiceXML handles all three so your Bubble app just sends data and receives a finished, validated file.
What you need
A free InvoiceXML account. Sign up and you receive 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. No other plugins.
Approach 1: Generate a complete ZUGFeRD from invoice data
This is the simplest path and the one to start with. You send your invoice data as JSON and InvoiceXML generates the human-readable PDF, the embedded XML, and the validated ZUGFeRD container, all in one call. You do not need to design or supply a PDF yourself.
Set up the API call
In the API Connector, add a new API named "InvoiceXML" and create a call:
- Name:
Create ZUGFeRD - Use as: Action
- Method: POST
- URL:
https://api.invoicexml.com/v1/create/zugferd - 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 so you can fill them from your database at runtime:
{
"invoice": {
"invoiceNumber": "<invoiceNumber>",
"issueDate": "<issueDate>",
"currency": "EUR",
"seller": {
"name": "<sellerName>",
"vatIdentifier": "<sellerVat>",
"postalAddress": {
"line1": "<sellerStreet>",
"city": "<sellerCity>",
"postCode": "<sellerPostCode>",
"country": "DE"
}
},
"buyer": {
"name": "<buyerName>",
"postalAddress": {
"line1": "<buyerStreet>",
"city": "<buyerCity>",
"postCode": "<buyerPostCode>",
"country": "DE"
}
},
"paymentDetails": {
"paymentAccountIdentifier": "<iban>"
},
"lines": [
{
"quantity": "<quantity>",
"priceDetails": { "netPrice": "<netPrice>" },
"vatInformation": { "rate": 19 },
"item": { "name": "<itemName>" }
}
]
}
}
Set the response type to file, because the endpoint returns a binary PDF, not JSON. Initialize the call, then switch the parameters to dynamic.
Multiple line items
The lines field is an array, so you add more invoice lines by including more objects in it:
"lines": [
{
"quantity": 10,
"priceDetails": { "netPrice": 150.00 },
"vatInformation": { "rate": 19 },
"item": { "name": "Consulting Services" }
},
{
"quantity": 2,
"priceDetails": { "netPrice": 80.00 },
"vatInformation": { "rate": 19 },
"item": { "name": "Support Package" }
}
]
Totals and the VAT breakdown are calculated automatically across all lines, so you never have to sum anything yourself. In Bubble, if your line items live in a list or a repeating group, you will build this array dynamically rather than hard-coding it (more on that below).
Use it in a workflow
Add the action Plugins → InvoiceXML - Create ZUGFeRD to a workflow, map each dynamic field from your database, and the returned file is available as the call's result. Save it to your invoice record, store it in S3, or trigger a download for the user.
Approach 2: Embed the XML into your own branded PDF
Approach 1 generates a clean PDF for you. But many Bubble apps already produce their own invoice PDFs, with custom branding, logos, and layouts, and want to keep that design. For this, you supply your PDF and InvoiceXML embeds the compliant XML into it.
The natural worry here is file handling. Bubble's API Connector is notoriously awkward with multipart file uploads, and embedding normally means uploading two files (your PDF plus the XML). To remove that friction entirely, the create endpoint accepts your PDF by URL instead of by upload.
Because every Bubble file already has a public URL, this fits Bubble perfectly. You pass the URL of your branded PDF in the options object and everything else stays as JSON:
{
"invoice": {
"invoiceNumber": "INV-2025-001",
"issueDate": "2025-07-01",
"currency": "EUR",
"seller": { "name": "Acme GmbH", "vatIdentifier": "DE123456789", "postalAddress": { "line1": "Musterstr. 1", "city": "Berlin", "postCode": "10115", "country": "DE" } },
"buyer": { "name": "Example Corp", "postalAddress": { "line1": "Hauptstr. 5", "city": "Munich", "postCode": "80331", "country": "DE" } },
"paymentDetails": { "paymentAccountIdentifier": "DE89370400440532013000" },
"lines": [ { "quantity": 10, "priceDetails": { "netPrice": 150.00 }, "vatInformation": { "rate": 19 }, "item": { "name": "Consulting Services" } } ]
},
"options": {
"pdfUrl": "https://yourapp.bubble.io/.../your-branded-invoice.pdf"
}
}
In one call you get three things: the EN 16931 XML generated from your data, that XML embedded into your branded PDF as a compliant PDF/A-3b, and a full ZUGFeRD compliance validation. No temporary XML storage, no second call, no multipart upload.
In your Bubble workflow, the pdfUrl value is simply the URL of the PDF you generated earlier (from a Bubble PDF plugin, an HTML-to-PDF service, or a stored file). Point the parameter at that file's URL and you are done.
A note on hosting: the PDF at that URL must be publicly reachable so InvoiceXML can fetch it, served with the correct content type and a valid content length. Bubble's stored file URLs satisfy this. If you host the PDF behind authentication, the fetch will fail.
Validating Factur-X and ZUGFeRD on Bubble
Whether you generate invoices or receive them from suppliers, you often need to confirm a file is compliant before sending it on or importing it. The validation endpoint checks a Factur-X or ZUGFeRD file against the full EN 16931 Schematron and PDF/A-3b conformance.
Set up a second API call:
- Name:
Validate ZUGFeRD - Method: POST
- URL:
https://api.invoicexml.com/v1/validate/facturx - Body type: Form-data
- Header:
Authorizationwith valueBearer YOUR_API_KEY - Add one parameter named
file, and check the box "This parameter is a file."
This is a file-upload call, so the "This parameter is a file" checkbox is essential. Without it, Bubble sends the file's URL as text and the validation receives nothing usable. To initialize, upload a sample ZUGFeRD file in the API Connector dialog, then switch the parameter to dynamic.
The response tells you whether the invoice is valid and, if not, lists the specific rule violations with plain-language messages (for example, that the total VAT does not match the sum of the VAT breakdown). You can surface these directly to your users so they know exactly what to fix. The full response structure is documented at invoicexml.com/docs.
One Bubble-specific tip: the API Connector treats some HTTP responses as errors by default. When wiring up validation, check how your call handles a response that reports an invalid invoice, and use Bubble's "Include errors in response and check for them" option on the call if you want to read the validation findings rather than have Bubble treat them as a hard error. The docs show the exact response shape so you can map the fields.
Building the line items array dynamically in Bubble
The one part of this 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 you have two clean options.
The first is to use a backend (API) workflow and Bubble's :format as text operator on a list of line items, joining each formatted line with a comma to assemble the array. You define the per-item JSON shape once and Bubble repeats it for each item in the list. This keeps everything inside Bubble.
The second is to assemble the JSON in a backend workflow step before the API call, which also keeps your API key off the client side. For any production app this is the better practice anyway, since the Authorization bearer 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 the PDF/A-3b container with the embedded fonts, ICC colour profile, XMP metadata, and the attachment relationship that German accounting systems like DATEV check on import. Bubble and generic PDF plugins do not produce conformant PDF/A-3b.
It runs the EN 16931 Schematron, the 200+ business rules that decide whether an invoice is genuinely accepted. This requires XSLT 2.0 processing that no Bubble plugin provides.
It validates before returning, so the file you get back has already passed compliance checks. You are not shipping invoices and hoping.
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 you are handling your customers' financial documents.
Endpoint summary
| Operation | Endpoint | Body | Returns |
|---|---|---|---|
| Create ZUGFeRD from data | POST /v1/create/zugferd | JSON | PDF/A-3b file |
| Create + embed into your PDF | POST /v1/create/zugferd (with options.pdfUrl) | JSON | PDF/A-3b file |
| Validate ZUGFeRD / Factur-X | POST /v1/validate/facturx | Form-data (file) | Validation result |
Factur-X works identically. Since Factur-X and ZUGFeRD are the same format, use /v1/create/facturx if you prefer the French naming, with the same request shape.
Full docs and interactive explorer: invoicexml.com/docs
Get started
Create a free InvoiceXML account →, grab your API key, and you can have a compliant ZUGFeRD 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 Peppol UBL invoices on Bubble
- E-invoicing in Salesforce: Peppol UBL, ZUGFeRD and Factur-X from Flow and Apex
- E-invoicing in Microsoft Power Automate and Dynamics 365
- ZUGFeRD API: the complete toolkit
- Factur-X API: the complete toolkit
- E-invoicing mandate deadlines by country
- Full API documentation
InvoiceXML is a REST API for European e-invoice compliance covering Factur-X, ZUGFeRD, XRechnung, Peppol UBL, and CII. 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.