Peppol E-Invoicing Integration: What Nordic Developers Need to Know Before 2028
A developer's guide to Peppol BIS Billing 3.0, EHF compliance, UBL formatting, and access point integration — with concrete timelines and implementation details.
Electronic invoicing is becoming mandatory across the Nordics. Norway leads with EHF/Peppol requirements tightening toward 2028. Denmark’s mandate kicks in for external accounting systems in January 2026. Sweden is investigating legislation. The EU is pushing B2B e-invoicing mandates that will reach every member state by 2030.
If you’re building or maintaining business software that handles invoices in the Nordic market, Peppol integration isn’t a nice-to-have — it’s a compliance requirement with a hard deadline.
This guide covers what you need to implement, when you need it, and the technical details that the vendor brochures leave out.
The Timeline
| Country | Mandate | Deadline | Standard |
|---|---|---|---|
| Norway | B2G mandatory, B2B required via Peppol | Jan 2028 (full B2B) | EHF 3.0 / Peppol BIS 3.0 |
| Denmark | B2B mandate for external accounting systems | Jan 2026 | Peppol BIS 3.0 via NemHandel |
| Denmark | B2B mandate for internal systems | Jul 2026 | Peppol BIS 3.0 via NemHandel |
| Sweden | Under investigation | TBD (EU directive by 2030) | Peppol BIS 3.0 (voluntary) |
| Finland | Widely adopted, no B2B mandate yet | TBD | Peppol BIS 3.0 (via Maventa) |
| Belgium | B2B mandate | Jan 2026 | Peppol BIS 3.0 |
| Poland | KSeF mandatory | Jul 2026 | National standard |
| France | B2B mandate (phased) | Sep 2026 | Factur-X / Peppol |
| Germany | B2B mandate (phased) | Jan 2027 | Peppol / XRechnung |
The wave is clear. Even if your country hasn’t mandated Peppol B2B yet, your trading partners in other countries will. Implementing now avoids a deadline scramble later.
What Peppol Actually Is
Peppol is three things:
1. A network. Peppol is a four-corner model connecting senders and receivers via access points (like email, but for business documents). You don’t send directly to your customer — you send to your access point, which routes it through the Peppol network to their access point.
[Your ERP] → [Your Access Point] → [Peppol SML/SMP] → [Their Access Point] → [Their ERP]
2. A document standard. Peppol BIS (Business Interoperability Specifications) Billing 3.0 defines the invoice format based on UBL 2.1 (Universal Business Language) and the European EN 16931 standard.
3. A governance framework. OpenPeppol manages the network, certifies access points, and maintains the specifications. Each country has a Peppol Authority that oversees local compliance.
The UBL Invoice Format
Peppol invoices are XML documents following the UBL 2.1 schema. Here’s a minimal valid Peppol BIS 3.0 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">
<!-- Document metadata -->
<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>INV-2026-0042</cbc:ID>
<cbc:IssueDate>2026-01-16</cbc:IssueDate>
<cbc:DueDate>2026-02-15</cbc:DueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>NOK</cbc:DocumentCurrencyCode>
<!-- Supplier (you) -->
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0192">987654325</cbc:EndpointID>
<cac:PartyIdentification>
<cbc:ID schemeID="0192">987654325</cbc:ID>
</cac:PartyIdentification>
<cac:PartyName>
<cbc:Name>Your Company AS</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Storgata 1</cbc:StreetName>
<cbc:CityName>Oslo</cbc:CityName>
<cbc:PostalZone>0155</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>NO</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>NO987654325MVA</cbc:CompanyID>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Your Company AS</cbc:RegistrationName>
<cbc:CompanyID schemeID="0192">987654325</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingSupplierParty>
<!-- Customer -->
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="0192">123456789</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>Customer Company AS</cbc:Name>
</cac:PartyName>
<!-- ... address, tax scheme ... -->
</cac:Party>
</cac:AccountingCustomerParty>
<!-- Tax summary -->
<cac:TaxTotal>
<cbc:TaxAmount currencyID="NOK">2500.00</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="NOK">10000.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="NOK">2500.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>25</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
<!-- Totals -->
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="NOK">10000.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="NOK">10000.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="NOK">12500.00</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="NOK">12500.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<!-- Line items -->
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="HUR">40</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="NOK">10000.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Consulting services</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>25</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="NOK">250.00</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
</Invoice>
That’s ~90 lines of XML for a single invoice with one line item. A typical invoice has 5-20 line items. The verbosity is the price of interoperability — every field has a precise semantic meaning defined by EN 16931.
Endpoint Identification
Every Peppol participant is identified by a combination of scheme ID and participant ID. In the Nordics:
| Country | Scheme ID | Identifier | Example |
|---|---|---|---|
| Norway | 0192 | Organization number (Enhetsregisteret) | 0192:987654325 |
| Sweden | 0007 | Organization number (Bolagsverket) | 0007:5567778899 |
| Denmark | 0184 | CVR number | 0184:12345678 |
| Finland | 0037 | Business ID (Y-tunnus) | 0037:12345678 |
You look up a participant’s access point via the SMP (Service Metadata Publisher) using their endpoint ID. The SML (Service Metadata Locator) maps the participant ID to the correct SMP.
Access Points
You don’t connect directly to the Peppol network. You connect through a certified access point (AP) — a service provider authorized by OpenPeppol to send and receive documents.
Major Nordic access points:
| Provider | Coverage | Integration |
|---|---|---|
| Qvalia | Sweden, Nordics | REST API, webhooks |
| InExchange | Sweden, Nordics | API, SFTP |
| Pagero (Thomson Reuters) | Global | REST API |
| Maventa (Visma) | Finland, Nordics | REST API |
| EHF.no | Norway | API |
| Unit4 | Nordics | API |
Each access point has its own API for submitting and receiving documents. There’s no standardized access point API — each provider implements their own integration layer.
Typical integration flow:
flow: erp-to-peppol
description: "Send approved invoices via Peppol"
trigger:
source: erp
event: invoice.approved
filter:
delivery_method: peppol
steps:
# 1. Look up recipient's Peppol endpoint
- lookup:
smp_query: "{{ trigger.customer.peppol_id }}"
result: recipient_endpoint
# 2. Transform ERP invoice to UBL format
- transform:
template: peppol-bis-3.0
input: "{{ trigger.invoice }}"
country_profile: "{{ trigger.customer.country }}"
output: ubl_invoice
# 3. Validate against Peppol rules
- validate:
document: "{{ ubl_invoice }}"
rules:
- peppol-bis-3.0
- "{{ country_rules[trigger.customer.country] }}"
on_failure: reject_with_details
# 4. Submit to access point
- submit:
access_point: qvalia
document: "{{ ubl_invoice }}"
recipient: "{{ recipient_endpoint }}"
# 5. Track delivery status
- monitor:
wait_for: delivery_receipt
timeout: 24h
on_timeout: alert_ops
Country-Specific Profiles
While Peppol BIS 3.0 is the base standard, each Nordic country has local requirements layered on top:
Norway (EHF 3.0)
- Aligned with Peppol BIS 3.0 / EN 16931
- Norwegian organization numbers required (scheme 0192)
- MVA (VAT) number format:
NOXXXXXXXXMVA - KID (Kundeidentifikasjon) payment reference for Norwegian bank payments
- Mandatory registration in ELMA (Elektronisk Mottaksadresse) by 2028
Denmark (NemHandel)
- Built on Peppol but accessed via NemHandel infrastructure
- CVR numbers required (scheme 0184)
- Integration with NemKonto for payment routing
- EAN location numbers used alongside Peppol IDs
Sweden
- Standard Peppol BIS 3.0
- Swedish organization numbers (scheme 0007)
- No mandatory B2B requirements yet — but Swedish public sector already requires Peppol
- Bankgiro/Plusgiro payment references common
Finland
- Standard Peppol BIS 3.0 via Maventa or other access points
- Y-tunnus business IDs (scheme 0037)
- OVT (Organisaatioiden Valitunniste) identifiers for public sector
- Finnish banking references (viitenumero)
Validation
Peppol documents must pass validation before transmission. There are three levels:
- XML Schema validation — Is it valid UBL 2.1 XML?
- Peppol BIS rules — Does it meet Peppol BIS 3.0 requirements? (Schematron rules)
- Country-specific rules — Does it meet EHF/NemHandel/local requirements?
Tools for validation:
- Peppol Testbed — OpenPeppol’s official validation service
- Vefa Validator — Open-source tool used by Difi (Norwegian Digitalisation Agency)
- Access point validation — Most access points validate before transmission
Implement validation in your pipeline before submitting to the access point. A rejected document means a failed invoice delivery — your customer doesn’t get the invoice, your cash flow is delayed.
Implementation Checklist
For a development team implementing Peppol integration:
- Register with an access point — sign a Peppol service provider agreement
- Register in SMP — publish your Peppol endpoint ID and supported document types
- For Norway: register in ELMA — the Norwegian Peppol participant registry
- Implement UBL generation — transform your invoice data model to Peppol BIS 3.0 UBL XML
- Handle country-specific fields — KID (NO), EAN (DK), Bankgiro (SE), viitenumero (FI)
- Implement validation — XML schema + Peppol Schematron + country rules
- Build the submission pipeline — ERP → transform → validate → access point
- Handle responses — delivery receipts, rejections, error responses
- Implement receiving — if you also receive invoices via Peppol (access point webhook/polling)
- Test with the Peppol Testbed — end-to-end testing before going live
- Monitor and alert — track delivery rates, rejection rates, validation errors
The Integration Platform Angle
The complexity of Peppol integration — UBL generation, multi-country profiles, access point APIs, validation rules — is exactly the kind of problem that benefits from a declarative, AI-assisted approach.
Instead of writing custom XML generation code per country, a declarative config describes the transformation rules. Instead of manually tracking Peppol specification changes, self-healing detects when a validation rule changes and updates the transformation accordingly.
The 2028 Norwegian deadline is 22 months away. The Danish deadline has already passed for some businesses. If your ERP doesn’t handle Peppol natively — and most don’t, fully — your integration platform needs to fill the gap.
Start now. The XML won’t write itself.