Electronic Invoicing 2026 for a B2B SaaS in France
Official timeline, PDP and e-reporting vocabulary, generating a Factur-X, and the role of a connector like iopole for a French B2B SaaS.
What's changing, and for whom
France's B2B electronic invoicing reform isn't a distant deadline anymore. The first wave is already in force. If your SaaS sells to French businesses, or you're building for that market, this is relevant to your roadmap today, not after launch.
Per the official government timeline (economie.gouv.fr, impots.gouv.fr):
- September 1, 2026: every VAT-registered business in France, regardless of size, must be able to receive electronic invoices. Large companies and mid-sized enterprises (ETI) must additionally issue invoices electronically and transmit their transaction and payment data to the tax administration (e-reporting).
- September 1, 2027: SMEs and micro-businesses get one extra year, then face the same issuance and e-reporting obligations.
If you edit a French B2B SaaS, your customers are affected today on the receiving side, and all of them will be by 2027 on the issuing side.
The vocabulary, without the jargon
- PDP (Plateforme de Dématérialisation Partenaire): a private operator approved by the French tax administration to issue, receive, and transmit electronic invoices and e-reporting data on a company's behalf. That's the role iopole plays in HeartCo's code.
- Annuaire (directory): the central registry that maps each French business ID (SIRET) to the PDP(s) it's reachable through, so an invoice routes to the right recipient without manual configuration.
- e-reporting: reporting to the tax administration the data an electronic invoice doesn't cover on its own: B2C sales, international transactions, cash receipts. This applies to almost any SaaS that also sells outside French B2B.
- Factur-X: the Franco-German hybrid format that embeds a structured XML file (the data) inside a human-readable PDF (the invoice you already know). This is the format the reform expects in practice for most B2B flows.
- Peppol network: the interoperable exchange network between PDPs, used for international routing and increasingly for domestic B2B too.
What a SaaS actually has to issue and receive
In practice, a Factur-X invoice isn't a PDF with extra metadata bolted on. It's a normalized document that has to satisfy verifiable consistency rules. The most common profile, Basic, follows the European semantic model EN 16931 and requires, among other things:
- the total VAT amount equal to the sum of the tax lines (rule
BR-CO-10, 0.01€ tolerance), - the total including tax equal to the total excluding tax plus total VAT (
BR-CO-13), - a valid seller SIRET (14 digits, Luhn checksum on the SIREN),
- a correctly formatted intra-community VAT number,
- dates in the
YYYYMMDDcalendar format.
The most common trap: rounding each line separately before summing, instead of summing then rounding. The one-cent discrepancy that results is enough to fail validation on the recipient's side, and it's the kind of bug that only shows up in production, on a real invoice with amounts that don't round evenly.
Generating a Factur-X: PDF/A-3 and CII XML
A Factur-X is a PDF/A-3 file (the sub-format that allows embedded attachments) containing an XML file following the UN/CEFACT CII (Cross Industry Invoice) syntax. The typical pipeline:
// Simplified schema: map your invoice data, generate the XML, embed it in the PDF
const invoice = {
profile: "BASIC",
invoiceNumber: "INV-2026-0001",
issueDate: "20260922",
seller: {
name: "Your company",
siret: "73282932000074",
vatNumber: "FR73282932000",
},
buyer: {
name: "Client SCI",
address: { postalCode: "92100", city: "Boulogne", countryCode: "FR" },
},
taxLines: [
{
categoryCode: "S",
ratePercent: 20,
basisAmount: 1000,
calculatedAmount: 200,
},
],
totalHT: 1000,
totalTVA: 200,
totalTTC: 1200,
};
// existingPdfBytes: the invoice PDF you're already generating
const { pdfBytes, validation } = await generateFacturX(
invoice,
existingPdfBytes,
);
if (!validation.isValid) {
// validation.errors: the list of failing blocking rules (BR-CO-*, SIRET format, etc.)
}In HeartCo, this pipeline (mapping Prisma data to Factur-X fields, generating the XML, embedding it in the PDF, validating it) is a dedicated module with its own unit tests for every validation rule, rather than a single hard-to-evolve function.
Connecting to an approved platform
Generating a valid Factur-X isn't enough. The reform requires the document to go through an approved operator, not email or a manual upload. That's the role of a PDP integration:
- Issuing: send the generated invoice to the PDP, which routes it to the recipient (through the directory, potentially over the Peppol network).
- Receiving: get a webhook when an invoice arrives, extract its data, and attach it automatically to the right customer organization.
- Lifecycle: a transmitted invoice isn't a one-off event, it's a state machine (submitted, transmitted, accepted, rejected, disputed...) that needs to stay in sync over time, not just at the moment it's sent.
- e-reporting: separately declaring B2C and international transactions, on a periodic basis.
That's exactly what HeartCo's iopole integration covers: an HTTP client with retries and authentication, a webhook handler that verifies the HMAC signature, and scheduled jobs that resync statuses without manual intervention. The Factur-X module (generation, validation) and the iopole integration (transmission, lifecycle) are two separate layers: the first builds a valid document, the second makes it circulate legally.
Test it before you trust it
An invoice that silently fails on the recipient's side is expensive in support tickets. The validation rules above are worthless if they aren't run automatically on every code change:
// Example business-rule test
it("rejects an invoice whose total doesn't match subtotal + VAT", () => {
const invoice = buildInvoice({
totalHT: 1000,
totalTVA: 200,
totalTTC: 1150,
});
const result = validateFacturX(invoice);
expect(result.isValid).toBe(false);
expect(result.errors).toContainEqual(
expect.objectContaining({ code: "BR-CO-13" }),
);
});A full round trip (generate an XML, parse it back, verify you land on the same data) catches regressions that a test isolated to generation alone never sees.
What's still on you
A generation module and a PDP integration don't make your SaaS compliant on their own. Still your responsibility, on the product side:
- The PDP account in your name (or your client's), with its own KYB process
- Choosing the Factur-X profile that fits your flows (Basic covers most simple B2B cases)
- The mandatory disclosures on the invoice itself, which evolve with the reform
- Actual e-reporting filing if you also sell B2C or internationally
- Legal retention of issued and received invoices
One methodology note: the reform keeps evolving, exact obligations depend on your situation, and this article isn't legal advice. For edge cases, an accountant or a DPO remains the right resource. Our free compliance checklist covers GDPR alongside invoicing, for an overview before you launch.
Go further
Related articles
Steering Claude Code with CLAUDE.md on a Multi-Tenant SaaS
The real rules that govern this SaaS with Claude Code: what goes in CLAUDE.md, what doesn't, and what's actually shipped at purchase.
ReadShipFast vs HeartCo: Which SaaS Boilerplate Should You Choose in 2026?
A direct, honest comparison of ShipFast and HeartCo: stack, pricing, features, and which kind of project each one actually fits.
ReadWhich Next.js SaaS Boilerplate Should You Pick in 2026? (Full Comparison)
ShipFast, MakerKit, SupaStarter, HeartCo: how to pick a Next.js SaaS boilerplate for your project, stack, and budget.
ReadReady to launch your SaaS?
HeartCo Starter includes everything you need: auth, payments, AI, mobile, audited security. Starting at $219.