ZUGFeRD, XRechnung and Peppol: e-invoicing formats explained for software builders
ZUGFeRD, XRechnung and Peppol BIS Billing 3.0 all carry the same invoice data, defined by the European standard EN 16931, in different packaging. ZUGFeRD (the same standard as France's Factur-X) is a PDF/A-3 file with CII XML embedded inside it. XRechnung is Germany's pure-XML specification for invoices to public-sector buyers, in UBL or CII. Peppol BIS Billing 3.0 is a UBL specification for invoices sent across the Peppol network. If you build invoicing software, model the EN 16931 data once and write a serialiser per format.
The standard underneath: EN 16931
EN 16931 was written by CEN, the European standards body, at the request of the European Commission. Part 1 is a semantic data model of the core elements of an electronic invoice. It is a model, not a file format. The standard binds that model to two XML syntaxes: UBL 2.1 and UN/CEFACT Cross Industry Invoice (CII).
Each national or network format is a CIUS (Core Invoice Usage Specification): a set of extra rules layered on EN 16931. XRechnung and Peppol BIS Billing 3.0 are both CIUSes. That is the key idea for a software builder. The formats disagree about packaging and about a handful of mandatory fields, not about what an invoice is.
The formats side by side
| ZUGFeRD / Factur-X | XRechnung | Peppol BIS Billing 3.0 | |
|---|---|---|---|
| Container | PDF/A-3 with embedded XML | XML only | XML only |
| XML syntax | UN/CEFACT CII | UBL 2.1 or CII | UBL |
| Maintained by | FeRD (Germany) and FNFE-MPE (France) | KoSIT, for the IT-Planungsrat | OpenPeppol |
| Human-readable layer | Yes, the PDF | No | No |
| Main use | Hybrid invoices for people and software | Invoices to German public-sector buyers | Exchange over the Peppol network |
| Relation to EN 16931 | BASIC, EN 16931 and EXTENDED profiles comply; MINIMUM and BASIC WL do not | CIUS | CIUS |
ZUGFeRD and Factur-X
A ZUGFeRD invoice looks and prints like any other PDF. Inside it, as a PDF/A-3 attachment, sits a CII XML file with the structured data. FNFE-MPE states plainly that Factur-X is the same standard as ZUGFeRD; the German and French bodies maintain it together. It comes in five profiles: MINIMUM, BASIC WL, BASIC, EN 16931 and EXTENDED. Only BASIC and above comply with EN 16931.
XRechnung
XRechnung is Germany's implementation of EU Directive 2014/55/EU for electronic invoices to the public sector. It is XML with no human-readable layer. Version 3.0 has applied since 1 February 2024. Public buyers route invoices using the Leitweg-ID, an addressing scheme XRechnung defines for federal, state and municipal authorities.
Peppol BIS Billing 3.0
Peppol is a network, not just a file format. Businesses send and receive documents through Peppol-accredited service providers in a four-corner model, so sender and receiver can use different providers. BIS Billing 3.0 is the invoice format on that network: UBL, identified by the CustomizationID urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 and the ProfileID urn:fdc:peppol.eu:2017:poacc:billing:01:1.0. On top of EN 16931 it requires, among other rules, a buyer reference or purchase order reference (PEPPOL-EN16931-R003) and an electronic address for both seller and buyer (R020 and R010).
ZUGFeRD vs XRechnung under the German B2B mandate
Since 1 January 2025 every domestic business in Germany must be able to receive an e-invoice. Issuing is phased. From 1 January 2025 to 31 December 2026 any issuer may still send a paper invoice, or another electronic format such as a PDF by email if the recipient agrees. For issuers whose previous-year turnover is 800,000 euros or less, that period runs to the end of 2027, and an EDI process that does not meet the e-invoice definition may also continue until then. After that, invoices between domestic businesses must be e-invoices.
The Federal Ministry of Finance (BMF) names XRechnung and ZUGFeRD from version 2.0.1 as meeting the requirements, except the MINIMUM and BASIC-WL profiles. A plain PDF sent by email is not an e-invoice. Where the PDF and the XML of a hybrid invoice disagree, the structured XML is authoritative. So for German B2B, ZUGFeRD in the EN 16931 profile and XRechnung are both acceptable, and the choice comes down to what the recipient's software reads.
How I built it in TidyBill
TidyBill is the invoicing SaaS I build and run. It supports two formats, and the design follows directly from EN 16931 being a model rather than a file.
- Map once. Every invoice is mapped into one internal document: header, seller, buyer, lines, tax breakdowns, totals, payment means. Nothing format-specific lives in the mapper.
- Validate before serialising. A validator checks required party fields, VAT IDs where the tax category needs them, ISO 4217 currency, exemption reason codes on non-standard-rate lines, and recomputes the tax breakdown against the totals. The Peppol validator adds the buyer-reference and electronic-address rules above.
- Serialise per format. ZUGFeRD uses the
horstoeko/zugferdPHP library in the EN 16931 profile, and the XML is merged into a PDF/A-3. Peppol UBL usesnum-num/ubl-invoice, sets the BIS 3.0 CustomizationID and ProfileID, and goes out as a separate XML attachment beside a plain PDF.
I chose the EN 16931 profile rather than MINIMUM or BASIC WL because those two are not valid e-invoices under the German rules. Each line's VAT category is inferred from the seller's country, the buyer's country and whether the buyer has a VAT ID, and can be overridden per line. A company switches e-invoicing on once, and a per-client setting overrides it either way.
One decision mattered more than the rest: a failed e-invoice never blocks the email. If validation fails, the customer still gets a plain PDF, and the invoice page shows exactly which fields are missing. An invoicing tool that refuses to send an invoice because a unit code is blank loses the user on day one.
TidyBill writes Peppol-format UBL but does not connect to an access point. Sending over the Peppol network needs an accredited service provider, which is a separate integration.
Validating against KoSIT, not against myself
My own validator catches the common mistakes, but it is my reading of the rules. The authority is the official Schematron. KoSIT publishes a Java validator that has no rules of its own and loads them from a scenario configuration; the XRechnung configuration carries the EN 16931 and XRechnung rules for UBL and CII.
TidyBill's CI has a job for this. On any pull request that touches e-invoicing code, it downloads KoSIT validator 1.6.2 and the XRechnung 3.0.2 configuration, checks the downloads against pinned SHA-256 hashes, generates seven fixture invoices (domestic standard rate, intra-EU reverse charge, export, multiple VAT rates, discounted, hourly time entries, service units), and runs each through the validator. Any error or rejected report fails the build. A unit test that compares my XML to a stored copy only proves the XML did not change. The KoSIT job proves it is still valid.
What I would tell another builder
- Model EN 16931 first and keep syntax out of the domain layer. Adding the second format then costs one serialiser.
- Run the official validator in CI from the first commit.
- Never let e-invoice generation block delivery of the invoice itself.
E-invoicing is compliance work where a wrong turn is expensive, the same situation I describe in what a fractional CTO does. More of my work is on the homepage.
I take on fractional CTO and senior architecture contracts through Apex Technology, working remotely. Email info@zayinkrige.com or read the full CV.