Integrating E-Invoice Compliance Into Your ERP
How to add a compliance layer to your ERP product: architecture, integration patterns, code examples, and the Evidence Pack workflow.
Your ERP generates invoices. Your customers need those invoices to be compliant with EN 16931, Peppol BIS, XRechnung, and whatever country-specific rules apply to their receivers. You can build all of this in-house — or you can add a compliance layer and ship it next quarter.
Why ERP Vendors Need a Compliance Layer
Three forces are converging:
Customer demand. "Can your ERP send Peppol invoices?" is now a standard question in procurement evaluations across Europe. Belgium's B2B mandate went live January 2026. Germany is phasing in B2B requirements. France is next. Your customers need this to work.
Mandate pressure. Each new country mandate adds a CIUS with its own validation rules, identifier requirements, and format nuances. Belgium needs KBO/BCE numbers with scheme ID 0208. Germany needs Leitweg-IDs in BT-10. The Netherlands uses KVK numbers with scheme 0106. Each country is a new set of rules to implement and maintain.
The alternative. Building e-invoicing compliance in-house means implementing the four validation layers, maintaining Schematron rules for every supported country, handling rule updates 2-3 times per year, and testing against every format combination. For a single country, this is feasible. For 10+ EU markets, it's a dedicated team.
A compliance API turns months of development into a single integration point.
Architecture: Where the Gate Sits
The compliance gate is an API call positioned between your ERP's invoice export and the delivery channel (Peppol Access Point, government portal, or email):
ERP Invoice Export → [Compliance Gate API] → Access Point → Peppol Network
↓ (if invalid)
Error Queue / UI
The gate is stateless. It receives an invoice, validates it, optionally remediates structural issues, and returns the result. It doesn't store invoices. It doesn't manage state. Your ERP remains the system of record.
This means the integration is lightweight — a single HTTP call in your invoice export pipeline. No database changes, no schema migrations, no new infrastructure to manage.
Integration Pattern: Synchronous API Call
The most common pattern. Validate every invoice before sending it to the Access Point.
import { InvoiceNavigator } from '@invoice-navigator/sdk';
const client = new InvoiceNavigator({
apiKey: process.env.INVOICE_NAVIGATOR_API_KEY
});
async function processInvoice(invoiceXml: Buffer) {
// Validate + remediate in one call
const result = await client.process({
invoice: invoiceXml,
remediate: true,
format: 'auto'
});
switch (result.status) {
case 'compliant':
// Invoice is valid — send to Access Point
await sendToAccessPoint(result.invoice);
// Store Evidence Pack for audit
await storeEvidencePack(result.evidencePack);
break;
case 'input_required':
// Missing data — queue for user input
await queueForInput(result.inputFields);
break;
case 'blocked':
// Financial field issues — route for review
await routeForReview(result.blockedErrors);
break;
}
}
Error Handling
async function processWithRetry(invoiceXml: Buffer) {
try {
const result = await client.process({
invoice: invoiceXml,
remediate: true
});
return result;
} catch (error) {
if (error.status === 429) {
// Rate limited — wait and retry
await delay(error.retryAfter * 1000);
return processWithRetry(invoiceXml);
}
if (error.status >= 500) {
// API unavailable — queue for later
await queueForRetry(invoiceXml);
return null;
}
throw error;
}
}
The key design principle: if the compliance API is temporarily unavailable, queue the invoice for retry rather than sending it unvalidated. A delayed compliant invoice is always better than a rejected non-compliant one.
Integration Pattern: Async Batch
For high-volume pipelines where latency isn't critical, batch processing reduces API calls and simplifies error handling.
async function processBatch(invoices: Buffer[]) {
const results = await client.processBatch({
invoices: invoices.map(xml => ({
invoice: xml,
remediate: true,
format: 'auto'
}))
});
const compliant = results.filter(r => r.status === 'compliant');
const needsInput = results.filter(r => r.status === 'input_required');
const blocked = results.filter(r => r.status === 'blocked');
// Send compliant invoices to Access Point
await Promise.all(compliant.map(r => sendToAccessPoint(r.invoice)));
// Queue the rest for handling
await queueForInput(needsInput);
await routeForReview(blocked);
}
When to use batch: overnight processing runs, hourly pipeline sweeps, or any scenario where invoices can wait minutes to hours before sending. Batch processing is also more cost-efficient — bulk API calls typically have lower per-invoice pricing.
Integration Pattern: Webhook
For event-driven architectures, webhooks let the compliance API call back to your system when processing is complete.
// 1. Submit invoice for async processing
const job = await client.submitAsync({
invoice: invoiceXml,
remediate: true,
webhookUrl: 'https://your-erp.com/api/compliance-callback'
});
// 2. Your webhook endpoint receives the result
app.post('/api/compliance-callback', async (req, res) => {
const { jobId, status, invoice, evidencePack, errors } = req.body;
if (status === 'compliant') {
await sendToAccessPoint(invoice);
await storeEvidencePack(evidencePack);
} else {
await handleErrors(jobId, status, errors);
}
res.sendStatus(200);
});
When to use webhooks: microservice architectures where the invoice export service shouldn't block waiting for validation, or when processing takes longer (e.g., ZUGFeRD PDFs that require PDF/A-3 conformance checking).
Authentication & API Key Management
Use separate API keys for each environment:
| Environment | Purpose | Rate Limits | |-------------|---------|------------| | Development | Testing with sample invoices | Low (sandbox) | | Staging | Integration testing with real formats | Medium | | Production | Live invoice processing | Per your plan |
# Development
INVOICE_NAVIGATOR_API_KEY=inv_dev_xxxxxxxx
# Production
INVOICE_NAVIGATOR_API_KEY=inv_prod_xxxxxxxx
The sandbox environment accepts any API key prefixed with inv_dev_ and returns realistic validation results without counting against your production quota. Use it for:
- CI/CD pipeline validation checks
- Template regression testing
- Developer onboarding
Evidence Pack Integration
The Evidence Pack is the compliance proof for every processed invoice. It contains the original invoice, validation report, remediation log, and revalidation result.
Storing Evidence Packs
Link each Evidence Pack to your invoice record:
async function storeEvidencePack(
invoiceId: string,
evidencePack: EvidencePack
) {
// Store the pack (S3, database, or document management system)
const packUrl = await storage.upload(
`evidence-packs/${invoiceId}.json`,
evidencePack
);
// Link to invoice record
await db.invoices.update(invoiceId, {
evidencePackUrl: packUrl,
complianceStatus: 'verified',
validatedAt: new Date()
});
}
Audit Retrieval
When a tax authority or trading partner requests proof of compliance, the Evidence Pack provides:
- The original invoice as submitted
- Every validation rule checked and its result
- Every auto-fix applied, with before/after values
- The final revalidation confirming compliance
This is the difference between "we think this invoice was valid" and "here's the documented proof." For businesses operating under Belgium's mandate, Germany's B2G requirements, or any country where compliance is legally required, the Evidence Pack is the audit trail.
Get started with the API → or see pricing →
FAQ
How long does API integration take?
For a synchronous pre-submission gate, most teams ship the initial integration in 1-2 days. The API call itself is straightforward — the work is in wiring it into your existing invoice export flow and handling the three result states (compliant, input required, blocked).
What happens if the API is down?
Queue the invoice for retry. Never send an unvalidated invoice to the Access Point — the cost of a rejection far exceeds the cost of a brief delay. Our API has 99.9% uptime SLA on business plans.
Do I need to handle different API calls per country?
No. The API auto-detects the invoice format and applies the correct rule set. A single process() call handles Peppol BIS, XRechnung, ZUGFeRD, Factur-X, and all supported country CIUS rules. You don't need country-specific integration logic.
Can I use this for template testing in CI/CD?
Yes. Generate a test invoice from your template, call the validation API, and fail the build if errors are returned. This catches template regressions before they reach production. Use the sandbox API key (inv_dev_*) for CI/CD — it doesn't count against your production quota.
Check Your Compliance Status
Find out exactly what your business needs to do for e-invoicing compliance.
Use Obligation FinderRelated Articles
E-Invoice Validation Rules Explained
EN 16931 defines ~65 business rules, Peppol adds ~80, and each country CIUS adds more. Here's how the four validation layers work, what they check, and which errors you'll see most.
The Real Cost of E-Invoice Rejections
When a Peppol invoice gets rejected, the cost isn't just the error fix. It's the payment delay, the support calls, and the trust erosion. Here's the full picture.
How to Validate E-Invoices Programmatically
Three approaches to e-invoice validation in your pipeline: build your own, use open-source tools, or call an API. Code examples for each.