Common Peppol BIS validation errors and how to fix them
By evoicely · Last checked 20 October 2026
A Peppol BIS Billing 3.0 invoice is checked against two rule sets: the EN 16931 rules (IDs starting with BR-) and OpenPeppol's own rules (IDs starting with PEPPOL-). Most errors mean either a Peppol-specific field is missing or malformed, or the totals don't add up exactly.
The ones that UBL generated from Stripe invoices hit most are PEPPOL-EN16931-R008 (an empty element), R010 and R020 (no electronic address), PEPPOL-COMMON-R043 (a Belgian enterprise number in the wrong format), R003 (no buyer or order reference), R001/R007 (business process), and the totals and VAT rules BR-CO-10, BR-CO-15, BR-S-08 and BR-AE-10.
Each section below gives the official rule text, why it fires, and the XML that satisfies it. To see which rules your own file breaks, upload it to our free validator.
Quick reference
| Rule | What's missing or wrong |
|---|---|
| PEPPOL-EN16931-R008 | An empty element |
| R020, R010, BR-62/63, CL008 | Seller or buyer electronic address (Peppol ID) |
| PEPPOL-COMMON-R043, R040 | Belgian enterprise number or GLN fails its format check |
| PEPPOL-EN16931-R003 | Neither buyer reference nor order reference |
| R001, R007, R004 | Business process or specification identifier |
| PEPPOL-EN16931-R002 | More than one invoice note |
| BR-CO-10, -13, -15, R120 | Line and document totals that don't add up |
| BR-S-08, BR-S-09, BR-CO-17 | VAT breakdown that ignores a discount |
| BR-AE-10, BR-AE-02 | Reverse charge without reason or buyer VAT ID |
| BR-CL-23, R051 | Unit and currency codes |
All of these are errors, marked "fatal" in the rule files, and Peppol access points validate invoices before sending them, so a file with any of them won't go out. Rule texts are quoted from the
Peppol BIS Billing 3.0 rules (release 3.0.21, which applies since 17 August 2026) and the
EN 16931 rules for UBL (1.3.16, from the EN 16931 validation artefacts).
In the rule files each message starts with its rule ID in brackets, such as [BR-CO-15]-; we leave that prefix off.
The XML fragments come from one complete fictional invoice, from a Belgian seller to a Belgian customer, that passes all of them. Each broken variant was checked to trigger the rule named.
For how the rule sets fit together and how to run them, see how to validate a Peppol BIS 3.0 invoice.
PEPPOL-EN16931-R008: empty elements
Document MUST not contain empty elements. Peppol rejects any element with no content and no children, and an element that holds only spaces counts as empty too. This is the most common Peppol error from home-made generators: a template writes every field, and when the Stripe value is null (a missing memo, a customer without a phone number, a card without a brand) the result is something like:
<cbc:Note></cbc:Note>
<cbc:NetworkID> </cbc:NetworkID>
The fix is in the generator: leave an element out when there's no value, and leave its parent out when all its children are gone. The error's location (for example …/CardAccount/NetworkID) tells you which element was empty.
If the element is required, an empty one doesn't satisfy the requirement either; you'll also get the rule for the missing field.
PEPPOL-EN16931-R020 and R010: electronic addresses
Seller electronic address MUST be provided (R020) and Buyer electronic address MUST be provided (R010). EN 16931 makes these optional; Peppol makes both mandatory, because they are the Peppol IDs the network routes on.
The seller's is BT-34, the buyer's BT-49, both as cbc:EndpointID at the start of cac:Party:
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="0208">0765432146</cbc:EndpointID>
<cac:PostalAddress>
...
Stripe has no field for a Peppol ID, so it has to come from your own settings (for the seller) and from customer metadata or a lookup (for the buyer). Three related errors show up when the element is there but its scheme is off:
- BR-62 / BR-63: The Seller electronic address (BT-34) shall have a Scheme identifier (BR-63 for the buyer). The
schemeIDattribute is missing. - PEPPOL-EN16931-CL008: Electronic address identifier scheme must be from the codelist "Electronic Address Identifier Scheme". The scheme isn't on the EAS code list as Peppol uses it.
A common cause is
EM(email), which XRechnung accepts but Peppol doesn't: an email address can't be used as a Peppol ID.
Common schemes are 0208 (Belgian enterprise number), 9925 (Belgian VAT number, written with the BE prefix), 0106 (Dutch KvK number), 0088 (GLN) and 9930 (German VAT number).
The ID only helps if the receiver is actually registered under it on Peppol; validation can't check that, your access point can.
PEPPOL-COMMON-R043 and R040: identifier format checks
PEPPOL-COMMON-R043: Belgian enterprise number MUST be stated in the correct format. Whenever scheme 0208 is used (in an endpoint, a party identifier or a legal registration ID), the value must be exactly ten digits whose last two are the mod-97 check digits of the first eight.
Three things trip it:
- The
BEprefix:BE0765432146is the VAT number format. With scheme 0208, write0765432146, or use scheme9925for the VAT number with prefix. - Dots or spaces, as enterprise numbers are often printed:
0765.432.146. - A typo, which the check digits catch:
0765432147fails,0765432146passes.
<cbc:EndpointID schemeID="0208">0765432146</cbc:EndpointID>
If you take the number from a Stripe customer's tax ID (type eu_vat, value BE0765432146), strip the country prefix and any punctuation before using it as a 0208 ID.
Peppol has similar checks for other national schemes; PEPPOL-COMMON-R040 (GLN must have a valid format according to GS1 rules) is the one for scheme 0088, where the 13th digit is a GS1 check digit.
Release 3.0.21 adds format checks for Dutch identifiers, as warnings. You may see them on invoices to customers in the Netherlands:
- PEPPOL-COMMON-R054: Dutch Chamber of Commerce (KVK) numbers (0106) MUST be stated in the correct format (12345678). Eight digits, no spaces.
- PEPPOL-COMMON-R056-2: Dutch VAT numbers MUST have the format (NL123456789B12). This one checks any VAT ID starting with
NL, including the buyer's VAT ID (BT-48), so a number typed with spaces or dots, or without itsB01-style suffix, triggers it. Write it without punctuation.
The release also has R055, R056-1 and R057 for the Dutch schemes 0190, 9944 and 0217.
PEPPOL-EN16931-R003: no buyer reference or order reference
A buyer reference or purchase order reference MUST be provided. Peppol requires at least one of the buyer reference (BT-10) or the purchase order reference (BT-13), so the buyer can match the invoice to something on their side. Stripe has no field for either, so a generator needs a default. If the customer gave you a PO number, use it as the order reference; otherwise a buyer reference such as their customer number or a contact name is enough:
<cbc:BuyerReference>PO-2026-118</cbc:BuyerReference>
Or, as an order reference (it comes after cac:InvoicePeriod, before the parties):
<cac:OrderReference>
<cbc:ID>PO-2026-118</cbc:ID>
</cac:OrderReference>
PEPPOL-EN16931-R001, R007 and R004: process and specification identifiers
- R001: Business process MUST be provided.
- R007: Business process MUST have an approved identifier.
- R004: Specification identifier MUST begin with the value
urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0and follow the format rules for the identifier.
Every Peppol BIS invoice starts with these two lines. The profile (BT-23) for billing is process 01:
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
A missing ProfileID gives R001 and R007 together. Since release 3.0.21, R007 accepts only a fixed list of approved profiles rather than any value of the right shape; for ordinary billing that's the one above.
A common R007 cause is writing the BIS version into it, …poacc:billing:3.0, instead of the process number.
R004 fires when the Peppol rules run on a file with a different specification identifier, for example a plain EN 16931 or an XRechnung one; access points run the Peppol rules on everything they send.
Since 3.0.21 it also rejects an identifier that starts correctly but contains :: anywhere after it, the sign of a badly joined extension.
Validators that choose the rules by the specification identifier, as ours does, check such a file as the standard it names instead, so they won't show R004 for it. If a file you meant for Peppol is reported as "EN 16931" or "XRechnung", look at CustomizationID first.
PEPPOL-EN16931-R002: more than one note
No more than one note is allowed on document level, unless both the buyer and seller are German organizations. UBL allows several cbc:Note elements; Peppol allows one.
A Stripe invoice has two free-text fields that end up here, the memo (description) and the footer. Join them into one note:
<cbc:Note>Thank you! Questions? billing@voorbeeld.example</cbc:Note>
The note comes right after cbc:InvoiceTypeCode; anywhere else, the XML schema rejects it.
BR-CO-10, BR-CO-13, BR-CO-15 and PEPPOL-EN16931-R120: totals
- BR-CO-10: Sum of Invoice line net amount (BT-106) = Σ Invoice line net amount (BT-131).
- BR-CO-13: Invoice total amount without VAT (BT-109) = Σ Invoice line net amount (BT-131) - Sum of allowances on document level (BT-107) + Sum of charges on document level (BT-108).
- BR-CO-15: Invoice total amount with VAT (BT-112) = Invoice total amount without VAT (BT-109) + Invoice total VAT amount (BT-110).
- PEPPOL-EN16931-R120: Invoice line net amount MUST equal (Invoiced quantity * (Item net price/item price base quantity) + Sum of invoice line charge amount - sum of invoice line allowance amount.
The document totals are checked to the cent. They break when some numbers are copied from Stripe (its subtotal, tax and total) and others are recomputed by the generator, with different rounding or without the discount.
Derive every total from the lines and the VAT breakdown you actually write:
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="EUR">49.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="EUR">49.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="EUR">59.29</cbc:TaxInclusiveAmount>
<cbc:PrepaidAmount currencyID="EUR">59.29</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="EUR">0.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
Here the customer has already paid by card, so the paid amount (BT-113) equals the total and the amount due (BT-115) is 0.
R120 is Peppol's line-level check: quantity × price must give the line amount. It fires on Stripe proration lines and similar adjustments, where the line amount is a fraction of the plan price. Write the price as the line's own amount divided by its quantity, not the plan's list price. R120 allows a small rounding slack; the document-level rules above don't.
BR-S-08, BR-S-09 and BR-CO-17: VAT breakdown with a discount
BR-S-08: For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is "Standard rated", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92) …
The usual cause is a Stripe coupon. If the discount is written as a document-level allowance, the VAT breakdown's taxable amount must be the amount after the discount. A breakdown that still shows the undiscounted 49.00 gets BR-S-08, and, because its VAT no longer matches taxable amount × rate, BR-S-09 and BR-CO-17 too. For a 20% coupon on a €49 plan at 21% Belgian VAT:
<cac:AllowanceCharge>
<cbc:ChargeIndicator>false</cbc:ChargeIndicator>
<cbc:AllowanceChargeReason>Coupon LAUNCH20</cbc:AllowanceChargeReason>
<cbc:Amount currencyID="EUR">9.80</cbc:Amount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:TaxCategory>
</cac:AllowanceCharge>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="EUR">8.23</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="EUR">39.20</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="EUR">8.23</cbc:TaxAmount>
...
The totals then read 49.00 (lines), 9.80 (AllowanceTotalAmount), 39.20 without VAT and 47.43 with VAT. With more than one VAT rate, give each allowance the rate of the lines it applies to and write one breakdown per rate.
BR-AE-10 and BR-AE-02: reverse charge
For a sale to a business in another EU country under the reverse charge, the VAT category is AE with rate 0, on the lines and in the breakdown.
- BR-AE-10: A VAT breakdown (BG-23) with VAT Category code (BT-118) "Reverse charge" shall have a VAT exemption reason code (BT-121), meaning "Reverse charge" or the VAT exemption reason text (BT-120) "Reverse charge" (or the equivalent standard text in another language).
- BR-AE-02: an invoice with reverse-charge lines must contain the seller's VAT ID (BT-31), tax registration ID (BT-32) or tax representative's VAT ID (BT-63), and the buyer's VAT ID (BT-48) or legal registration identifier (BT-47).
In Stripe that's a customer with tax_exempt set to reverse; the buyer's VAT ID comes from the invoice's customer_tax_ids. If the customer has no tax ID in Stripe, BR-AE-02 fires. The breakdown's category:
<cac:TaxCategory>
<cbc:ID>AE</cbc:ID>
<cbc:Percent>0</cbc:Percent>
<cbc:TaxExemptionReasonCode>VATEX-EU-AE</cbc:TaxExemptionReasonCode>
<cbc:TaxExemptionReason>Reverse charge</cbc:TaxExemptionReason>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:TaxCategory>
Put the reason only in the breakdown, not on the lines: in cac:ClassifiedTaxCategory it gives the warnings UBL-CR-600 and UBL-CR-601.
Whether reverse charge applies to a sale is a tax question; the rules only check that the invoice is consistent once you've decided.
BR-CL-23 and PEPPOL-EN16931-R051: unit and currency codes
- BR-CL-23: Unit code MUST be coded according to the UN/ECE Recommendation 20 with Rec 21 extension. Stripe has no unit of measure, and words like
monthorpcsaren't codes. UseC62("one") for a subscription or licence counted in units,MONfor a quantity in months,HURfor hours. - PEPPOL-EN16931-R051: All currencyID attributes must have the same value as the invoice currency code (BT-5), except for the invoice total VAT amount in accounting currency (BT-111).
Stripe writes currencies in lower case (
eur); in the XML they must be upper case (EUR), the same on every amount. A lower-case code also triggers BR-CL-03 and PEPPOL-EN16931-CL007.
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">49.00</cbc:LineExtensionAmount>
Checking your fix
Fix one group of errors at a time and check again: a missing parent element can hide errors in its children, and the XML schema check fails first if elements are in the wrong order. Our validator runs the EN 16931 and Peppol BIS 3.0 rules, lists each finding with its location in the document, and shows the invoice in readable form.
Related guides
- How to validate a Peppol BIS 3.0 invoice: the rule sets and how to run them.
- Belgium's Peppol mandate for Stripe users.
- Mapping a Stripe invoice to EN 16931: where each field comes from in Stripe.
- Stripe credit notes as e-invoices.
- Common XRechnung validation errors: the German BR-DE rules.
Where evoicely fits
Check your Peppol invoice for these errors, free and without signing up. evoicely sends Stripe invoices and credit notes over Peppol for Belgian businesses, checked against these rules before they go out, and turns them into XRechnung and ZUGFeRD for German customers. Get started: it's free for 10 e-invoices a month.
This is general information, not legal or tax advice. Passing the validation rules doesn't settle questions such as which VAT treatment applies; check what applies to your business with your tax advisor.