Skip to main content

Belgium ๐Ÿ‡ง๐Ÿ‡ช

Belgium's structured B2B e-invoicing mandate, its identifiers, and how GoRoute delivers there.

Overviewโ€‹

AspectDetails
B2B MandateMandatory since 1 January 2026 for invoices between Belgian VAT-liable businesses: structured e-invoices (Peppol BIS Billing 3.0) exchanged over the Peppol network. Businesses must be able to send and receive
B2G MandateRequired (federal since 2021, all levels 2024) via Mercurius
B2COut of scope: a sale to a private customer needs no structured e-invoice
FormatPeppol BIS Billing 3.0 (EN 16931)
Primary Scheme0208 (CBE/KBO enterprise number); 9925 (BE VAT number) also in use
AccreditationNone beyond Peppol certification: any certified Peppol Access Point may register Belgian businesses and deliver to them. GoRoute is certified (POP000991)
NextNear-real-time transactional reporting is targeted for 2028 in the federal coalition agreement; not yet law
Live mandate

Since 1 January 2026 a Belgian business invoicing another Belgian business must send a structured e-invoice over Peppol, and every Belgian VAT-liable business must be able to receive one. A PDF by email no longer counts between businesses. GoRoute delivers Belgian invoices today with no local accreditation required; the WooCommerce plugin does it from the checkout.

GoRoute enforces this. Asking us to deliver a Belgian seller's invoice by email is refused with the code EMAIL_NOT_ALLOWED rather than being sent โ€” see Delivery and formats.

Identifier Schemesโ€‹

CBE/KBO Number (0208) โ€” Standardโ€‹

The Crossroads Bank for Enterprises number:

# Format: 0/1 + 9 digits (10 total)
participant = {
"scheme": "0208",
"identifier": "0123456789",
"name": "Example BVBA",
"country": "BE"
}

Validation Rules:

  • 10 digits, starts with 0 or 1
  • Last 2 digits are check digits (mod 97)
  • Look up at KBO Public Search
def validate_cbe(cbe: str) -> bool:
"""Validate Belgian CBE/KBO number."""
if len(cbe) != 10:
return False
if cbe[0] not in ('0', '1'):
return False
if not cbe.isdigit():
return False
# Check digit validation
base = int(cbe[:8])
check = int(cbe[8:])
return 97 - (base % 97) == check

VAT Number (9925)โ€‹

For cross-border B2B:

# Format: BE + 10 digits (or BE + 0 + 9 digits)
participant = {
"scheme": "9925",
"identifier": "BE0123456789",
"name": "Example NV",
"country": "BE"
}

Government E-invoicing (B2G)โ€‹

Mercurius Platformโ€‹

All B2G invoices in Belgium go through Mercurius:

Supplier โ†’ GoRoute (Peppol AP) โ†’ Mercurius โ†’ Government Entity

Mercurius Requirementsโ€‹

  1. Peppol format (UBL 2.1 via BIS 3.0)
  2. CBE number as sender ID
  3. Correct Mercurius endpoint for receiver
  4. Structured references when required

Finding Government Endpointsโ€‹

# Lookup Belgian government entity
response = requests.get(
"https://app.goroute.ai/peppol-api/api/v1/participants/lookup",
params={
"scheme": "0208",
"identifier": "0671516647" # Example federal entity
},
headers={"X-API-Key": "your_api_key"}
)

Federal vs Regionalโ€‹

LevelPortalNotes
FederalMercuriusAll federal entities
FlandersMercuriusIntegrated
WalloniaMercuriusIntegrated
BrusselsMercuriusIntegrated
MunicipalitiesVariesMany via Mercurius

Invoice Requirementsโ€‹

Standard Belgian Invoiceโ€‹

<Invoice>
<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>

<cbc:ID>BE-INV-2024-00123</cbc:ID>
<cbc:IssueDate>2024-01-15</cbc:IssueDate>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>

<!-- Required for B2G -->
<cbc:BuyerReference>PO-2024-1234</cbc:BuyerReference>

<!-- Belgian Seller -->
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0208">0123456789</cbc:EndpointID>

<cac:PartyTaxScheme>
<cbc:CompanyID>BE0123456789</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>

<cac:PartyLegalEntity>
<cbc:RegistrationName>Voorbeeld BVBA</cbc:RegistrationName>
<cbc:CompanyID schemeID="0208">0123456789</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingSupplierParty>

<!-- Belgian Government Receiver -->
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="0208">0671516647</cbc:EndpointID>
</cac:Party>
</cac:AccountingCustomerParty>
</Invoice>

Structured Communicationโ€‹

Belgian B2G often requires structured payment reference:

<cac:PaymentMeans>
<cbc:PaymentMeansCode>30</cbc:PaymentMeansCode>
<!-- OGM/VCS structured communication: +++123/1234/12345+++ -->
<cbc:PaymentID>+++123/1234/12345+++</cbc:PaymentID>
<cac:PayeeFinancialAccount>
<cbc:ID>BE68539007547034</cbc:ID>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>

VAT Requirementsโ€‹

Belgian VAT Formatโ€‹

# Belgian VAT: BE + 10 digits
def validate_belgian_vat(vat: str) -> bool:
if not vat.startswith("BE"):
return False
number = vat[2:]
if len(number) != 10:
return False
if not number.isdigit():
return False
# Mod 97 check
return int(number[:8]) % 97 == 97 - int(number[8:])

VAT Ratesโ€‹

The Code column is the VAT category, which is a different field from the percentage: the category says why a rate applies, the percentage says what it is. Every positive Belgian rate is category S carrying its own percentage.

CodeRateDescription
S21%Standard rate
S12%Reduced (restaurants, social housing)
S6%Super-reduced
Z0%Zero rate (exports)
E0%Exempt
K0%Intra-community supply to a VAT-registered business in another member state
AA and H are rejected

Earlier revisions of this page gave AA for the 12% rate and H for 6%. Neither is a permitted VAT category. EN 16931 allows only AE, B, E, G, K, L, M, O, S and Z (UNCL5305), and rules BR-CL-17 and BR-CL-18 reject anything else โ€” the invoice fails validation and is never delivered. Send the reduced rates as S with 12 or 6 in the percentage.

Intra-community suppliesโ€‹

To invoice goods to a VAT-registered business in another EU member state through POST /api/v1/invoices, give the line "tax_category": "intra_community". GoRoute writes it as category K at 0% with exemption reason code VATEX-EU-IC and the text "Intra-Community supply" (your own code and text are kept if you send them; any code other than VATEX-EU-IC is refused). EN 16931's BR-IC rules then need four things, and the invoice is refused before anything is sent (error BUS_033, naming the field) if one is missing: seller.vat_number and buyer.vat_number (BR-IC-02); a rate of 0 or none (BR-IC-05); the actual delivery date delivery.date or an invoicing period billing_period_start / billing_period_end (BR-IC-11); and the country the goods were delivered to, delivery.address.country or delivery_country (BR-IC-12). The delivery country is never taken from the buyer's address: where the goods went is yours to state.

"delivery": {"date": "2026-09-25", "address": {"country": "LU"}},
"lines": [
{"description": "Office chairs", "quantity": 10, "unit_price": 120.00,
"tax_category": "intra_community"}
]
intra_community is refused on a PINT document, whichever party puts it there

VAT category K exists in EN 16931 only, which is the standard Peppol BIS Billing 3.0 and XRechnung are built on. A document that renders under a PINT profile has no intra-community category at all, so GoRoute refuses tax_category: intra_community on one before it checks anything else โ€” before the four BR-IC requirements above are even looked at. The refusal is the same error code, BUS_033, and its message names the profile the document renders under and tells you to use the category that profile provides.

GoRoute renders two PINT profiles today, and each is decided by a different party. Read this by the document, not by the seller:

  • PINT A-NZ is decided by the buyer's country on its own. A buyer whose address country is AU or NZ makes the document PINT A-NZ whoever the seller is, so your own invoice to an Australian or New Zealand buyer is refused this category. There is no Australian or New Zealand country pack in the platform, and nothing about the seller changes the choice. The other direction is therefore unaffected: an Australian or New Zealand seller invoicing an EU buyer renders Peppol BIS Billing 3.0 and can use the category exactly as you can.
  • PINT OM is decided by Oman's country pack: an Omani seller, an Omani buyer's own import record, an Omani buyer's self-billed document, or a buyer addressed under Peppol scheme 0248. So an Omani seller cannot send an intra-community line at all โ€” and neither can you, to a buyer addressed under 0248. Where that pack is not running, the older rule applies and either party being in Oman is enough. Which documents are Oman documents sets out both rules and how to tell which one your platform applies.

None of this blocks a real intra-community supply, which by definition goes to a VAT-registered business in another EU member state. What it catches is a European code path reused for every market โ€” the same mapping, the same exemption code, a buyer outside the EU.

Check it rather than reasoning about it. Send the invoice to POST /api/v1/invoices/validate. One call tells you whether the value is accepted for that document on the platform you are calling, and /validate applies the same rules as every path that sends.

Example Invoiceโ€‹

<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">

<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>

<cbc:ID>BE-RG-2024-00123</cbc:ID>
<cbc:IssueDate>2024-01-15</cbc:IssueDate>
<cbc:DueDate>2024-02-15</cbc:DueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>

<cbc:BuyerReference>BESTELBON-2024-001</cbc:BuyerReference>

<!-- Belgian Supplier -->
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0208">0123456789</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>Voorbeeld NV</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Koningsstraat 1</cbc:StreetName>
<cbc:CityName>Brussel</cbc:CityName>
<cbc:PostalZone>1000</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>BE0123456789</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Voorbeeld NV</cbc:RegistrationName>
<cbc:CompanyID schemeID="0208">0123456789</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingSupplierParty>

<!-- Belgian Government Buyer -->
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="0208">0671516647</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>FOD Financiรซn</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>North Galaxy</cbc:StreetName>
<cbc:CityName>Brussel</cbc:CityName>
<cbc:PostalZone>1030</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyLegalEntity>
<cbc:RegistrationName>FOD Financiรซn</cbc:RegistrationName>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>

<cac:PaymentMeans>
<cbc:PaymentMeansCode>30</cbc:PaymentMeansCode>
<cbc:PaymentID>+++123/1234/12345+++</cbc:PaymentID>
<cac:PayeeFinancialAccount>
<cbc:ID>BE68539007547034</cbc:ID>
<cbc:Name>Voorbeeld NV</cbc:Name>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>

<cac:PaymentTerms>
<cbc:Note>Betaling binnen 30 dagen</cbc:Note>
</cac:PaymentTerms>

<cac:TaxTotal>
<cbc:TaxAmount currencyID="EUR">21.00</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="EUR">100.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="EUR">21.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>

<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="EUR">100.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="EUR">121.00</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="EUR">121.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>

<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="HUR">10</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Consulting diensten</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="EUR">10.00</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
</Invoice>

Bilingual Considerationsโ€‹

Belgium has three official languages. Consider:

<!-- Item names can be multilingual -->
<cac:Item>
<cbc:Name>Consultancy services / Conseil / Advies</cbc:Name>
</cac:Item>

Resourcesโ€‹

Next Stepsโ€‹

Further readingโ€‹

Ready to build? The free developer sandbox gives you test credentials and a registered participant, provisioned within 24 hours on business days. Recorded ERP integration walkthroughs are on the tutorials page.