---
title: "France · E-invoicing integration playbook"
description: "Ship a French-compliant e-invoicing integration end to end: onboarding & annuaire, receiving, sending Factur-X, emitting the 200–213 lifecycle statuses, e-reporting, sandbox test matrix and go-live checklist — with copy-paste code for every step."
canonical: "https://docs.get-flowie.com/compliance/fr/integration"
source: "https://docs.get-flowie.com/compliance/fr/integration.html"
---

# France · E-invoicing integration playbook

Compliance · 🇫🇷 France

# The French e-invoicing integration playbook

## Why this playbook

From **1 September 2026** every French VAT-liable business must be able to _receive_ e-invoices — and large & mid-sized companies must also _send_ them. SMEs follow on **1 September 2027**. There is no free state exchange platform anymore (the PPF was reduced to the directory + tax concentrator in October 2024), so every invoice flows through a **Plateforme Agréée**. Flowie is PA n° `0064`: this page is the complete, code-first path from zero to a compliant integration — sending, receiving, and the [full 200–213 lifecycle](<lifecycle.html>).

Seven steps, each with copy-paste code. A focused team ships this in days, not months.

## Architecture in 60 seconds
[code] 
    Your ERP / app
       │  REST + webhooks (one API for every country)
       ▼
    Flowie (PA n° 0064) ── annuaire lookup ──► recipient's PA ──► buyer
       │
       └── e-reporting & mandatory statuses ──► PPF concentrator ──► DGFiP  (≤ 24 h)
[/code]

  * **You never talk to the PPF.** Flowie routes invoices PA-to-PA (resolved via the annuaire) and reports the mandatory data and statuses to the concentrator within the 24-hour window.
  * **One integration, both directions.** The same document API sends and receives; direction is just a field.
  * **Statuses are first-class.** Lifecycle statuses travel as CDAR messages between platforms; you emit and observe them through the lifecycle API and webhooks — never by parsing XML.



## Onboard your companies & the annuaire

Create each French legal entity with its SIREN/SIRET, then register it. Registration provisions the receiving flow and (for entities you manage) the **annuaire** entry that tells every other PA to route invoices for this SIREN to Flowie.
[code] 
    curl -X POST https://back.flowie.ink/exchange/v1/companies \
      -H "Authorization: Bearer $KEY" \
      -d '{
        "name": "Maison Lumière SAS",
        "country": "FR",
        "vatNumber": "FR26921376265",
        "additionalIdentifiers": { "siret": "92137626500017" }
      }'
    
    curl -X POST https://back.flowie.ink/exchange/v1/companies/comp_01H…/register \
      -H "Authorization: Bearer $KEY"
[/code]

Before invoicing a French counterparty, check how they’re reachable (their PA, their identifiers) with a [directory search](<../../reference/index.html#search-directory>) — a bare SIREN/SIRET is routed to an exact lookup:
[code] 
    curl "https://back.flowie.ink/exchange/v1/directory/search?q=552100554" \
      -H "Authorization: Bearer $KEY"
[/code]

When a counterparty exposes several **reception points** (_lignes annuaire_) on the same SIRET, address the exact one by passing the composed identifier `{siren}_{siret}[_{suffix}]` (e.g. `75297877500027_001`) as the send `to` — Flowie resolves the participant from the SIRET and carries the `suffixeAdressage` through as routing metadata. See [Reception-point addressing](<../../reference/index.html#reception-point-addressing>).

## Receive — you have to be ready first

Reception is the first legal deadline (all companies, September 2026) and the easy half. Register a webhook endpoint, and every inbound invoice — Factur-X, UBL or CII, from any PA — lands as a normalized document:
[code] 
    curl -X POST https://back.flowie.ink/exchange/v1/webhooks \
      -H "Authorization: Bearer $KEY" \
      -d '{
        "url": "https://erp.example.fr/hooks/flowie",
        "events": ["document.received", "lifecycle.updated",
                   "compliance.reported", "compliance.reported.failed",
                   "document.failed"]
      }'
[/code]

On `document.received`, pull whichever view your system prefers — the structured JSON, the original XML, or the rendered PDF:
[code] 
    curl https://back.flowie.ink/exchange/v1/documents/doc_inb1/structured \
      -H "Authorization: Bearer $KEY"     # flat JSON for your ERP
    # also available: …/xml (signed original) and …/pdf (human view)
[/code]

Receiving is also when your lifecycle duties start

The moment an invoice is made available to you ([status 203](<lifecycle.html#status-203>)), you are the buyer in the state machine: acknowledging, approving, disputing or refusing are _your_ calls to make — see step 4. 

## Send — Factur-X by default

The standard [send call](<../../reference/index.html#send-document>) works unchanged; for French domestic B2B, Flowie generates **Factur-X** (EN 16931, CIUS-FR) and routes via the annuaire. Mind the French required fields — SIRET, and since 2026 the buyer’s SIREN, delivery address when it differs, and the nature of the operation (goods / services / mixed):
[code] 
    curl -X POST https://back.flowie.ink/exchange/v1/documents/send \
      -H "Authorization: Bearer $KEY" \
      -H "Idempotency-Key: fa-2027-0042" \
      -d '{
        "type": "invoice",
        "from": "comp_01H…",
        "to":   "0009:55210055400013",
        "document": {
          "number": "FA-2027-0042",
          "issueDate": "2027-09-01",
          "dueDate":   "2027-10-01",
          "currency":  "EUR",
          "buyer": { "name": "Grand Client SA", "vatNumber": "FR40552100554",
                     "additionalIdentifiers": { "siren": "552100554" } },
          "lines": [
            { "description": "Prestation de conseil — août 2027",
              "quantity": 8, "unit": "days", "unitPrice": 950.00,
              "vatRate": 20, "vatCategory": "S" }
          ]
        }
      }'
[/code]

A `201` response means the invoice passed Flowie’s controls — [status `200 Déposée`](<lifecycle.html#status-200>) is emitted and its data will reach the PPF within 24 h. A platform rejection later (unknown SIRET, duplicate number…) surfaces as `document.failed` with the motif — that is [status `213 Rejetée`](<lifecycle.html#status-213>): fix and send a _new_ invoice, never a mutation of the old one. B2G invoices (Chorus Pro) additionally need `buyerReference` (Service Exécutant) and `orderReference` — see [required fields](<index.html#required-fields>).

## Emit the lifecycle — who owes which status

The [explorer](<lifecycle.html>) covers every status in depth; here is the split of responsibilities your integration must implement. Everything is one endpoint: [`POST /v1/documents/{id}/lifecycle`](<../../reference/index.html#update-lifecycle>).

When…| You call| FR status emitted| Tier  
---|---|---|---  
**As buyer (inbound invoices)**  
AP takes the invoice into processing| `{"status":"under_review"}`| `204` Prise en charge| Recommended  
You approve it in full| `{"status":"approved"}`| `205` Approuvée| Recommended  
You approve it in part| `{"status":"approved","remainingAmount":…}`| `206` Approuvée partiellement| Recommended  
You contest it (without refusing)| `{"status":"disputed","reason":"…"}`| `207` En litige| Libre  
You need supporting documents| `{"status":"disputed","reasonCode":"suspended","reason":"…"}`| `208` Suspendue| Libre  
You refuse it (business decision)| `{"status":"rejected","reasonCode":"…","reason":"…"}`| `210` Refusée| **Mandatory**  
Your payment went out| `{"status":"paid","paymentDate":"…"}`| `211` Paiement transmis| Recommended  
**As supplier (outbound invoices)**  
Buyer asked for documents (you received `208`)| `POST …/actions {"action":"link","relatedDocumentId":"…"}`| `209` Complétée| Libre  
The money arrived on your account| `{"status":"paid","paymentDate":"…","paymentAmount":…}`| `212` Encaissée| **Mandatory**  
Partial collection| `{"status":"partially_paid","paymentAmount":…,"remainingAmount":…}`| `212` Encaissée (partial)| **Mandatory**  
  
The two calls you cannot skip

`210` when you refuse as a buyer, `212` when you’re paid as a supplier. Both are legally mandatory, both are auto-reported by Flowie to the DGFiP, and `212` is what pre-fills the CA3 VAT return for services. Everything else improves visibility; these two keep you compliant. Statuses `200`, `201`, `202`, `203` and `213` are Flowie’s job — never emit them yourself. 

## The webhook handler — your single integration point

One endpoint, four event families, out-of-order-safe. This is the reference shape (Node; the logic transposes 1-to-1 to any stack):
[code] 
    import express from "express";
    import crypto from "node:crypto";
    
    const app = express();
    app.use(express.raw({ type: "application/json" })); // keep the raw body for HMAC
    
    // French statuses can be skipped (only 4 of 14 are mandatory) — never
    // assume ordering. Rank them and ignore stale updates.
    const RANK = { submitted: 0, received: 1, under_review: 2, disputed: 3,
                   approved: 4, partially_paid: 5, paid: 6, rejected: 9 };
    
    app.post("/hooks/flowie", async (req, res) => {
      const sig = crypto.createHmac("sha256", process.env.FLOWIE_WEBHOOK_SECRET)
                        .update(req.body).digest("hex");
      if (!crypto.timingSafeEqual(Buffer.from(sig),
            Buffer.from(req.headers["x-flowie-signature"] ?? ""))) {
        return res.status(401).end();
      }
      const event = JSON.parse(req.body);
      res.status(200).end();               // ack fast, process async
    
      switch (event.type) {
        case "document.received":          // FR code 203 — you are the buyer
          await queue.push("import-invoice", event.data.documentId);
          break;
    
        case "lifecycle.updated": {        // any 2xx status, either direction
          const { documentId, currentStatus, previousStatus, reasonCode, reason } = event.data;
          if (RANK[currentStatus] <= RANK[previousStatus]) break;  // stale / dup
          await erp.setInvoiceStatus(documentId, currentStatus, { reasonCode, reason });
          break;
        }
    
        case "document.failed":            // FR code 213 — technical reject
          await alerting.page("invoice-rejected", event.data);     // fix + re-send
          break;
    
        case "compliance.reported":        // DGFiP leg confirmed
        case "compliance.reported.failed": // DGFiP leg rejected — investigate
          await audit.log(event.type, event.data);
          break;
      }
    });
[/code]

Three properties make this production-grade, and all three matter in France specifically:

  * **Idempotent + monotonic** — statuses may arrive twice (retries) or out of order (skipped optional statuses); the rank check handles both.
  * **Fast ack** — status bursts happen (a buyer platform replaying a backlog); never do ERP writes before responding.
  * **Compliance events audited** — `compliance.reported` is your proof the mandatory statuses reached the DGFiP; keep the trail (FEC audits ask for it).



## E-reporting — mostly Flowie’s job, two things are yours

E-reporting covers what e-invoicing doesn’t: **B2C** and **international / intra-community** transactions (transmitted as data, not exchanged invoices), plus **payment data** for services. What that means for you:

  * **Domestic B2B** — nothing to do. The mandatory statuses you emit (esp. `212` with its collected amount) _are_ the payment e-reporting; Flowie forwards them on schedule.
  * **B2C & cross-border** — send those transactions through the same API (the recipient just isn’t on the French network); Flowie derives and files the transaction data at your VAT regime’s frequency.
  * **Watch the failures.** `compliance.reported.failed` means the DGFiP leg bounced; Flowie retries with backoff, but repeated failures (bad SIREN, closed period) need your action.



## Prove it in the sandbox — the French test matrix

Every scenario above is reproducible with a `flw_test_` key, deterministic [test identifiers](<../../sandbox/index.html>) and the `simulateCompliance` org flag:

What you’re proving| How| Expect  
---|---|---  
Happy path 200→212| Send, then walk `under_review → approved → paid`| `lifecycle.updated` ×3, then `compliance.reported` (with `simulateCompliance:"accept"`)  
Refusal (210) with motif| `{"status":"rejected","reasonCode":"…"}` as buyer| Terminal state; motif echoed verbatim in the event  
Platform reject (213)| `simulateCompliance: "reject_00058"`| `compliance.reported.failed` with code `00058`  
Illegal transition guard| `received → paid` directly| `409 invalid_transition` \+ allowed next states  
DGFiP outage resilience| `simulateCompliance: "timeout_30s"`| Delayed failure event — exercise your retry/alerting  
Flaky network| `simulateCompliance: "flaky_50pct"`| Random accept/fail — your handler must be idempotent  
  
## Go-live checklist

  * ☐ Every French entity created with SIREN/SIRET and **registered** (annuaire entry live — verify with a directory search on your own SIREN).
  * ☐ Webhook endpoint deployed, **HMAC-verified** , idempotent, monotonic — and subscribed to the five event types above.
  * ☐ Buyer-side flows wired: approve / partial-approve / dispute / **refuse-with-motif (210)**.
  * ☐ Supplier-side **encaissement (212)** wired to your bank reconciliation (incl. partial collections).
  * ☐ `213` alerting in place — a technical reject must page someone; the invoice legally doesn’t exist until re-sent.
  * ☐ Sandbox matrix green, including the timeout and flaky simulators.
  * ☐ Sending: French required fields present (SIRET, buyer SIREN, delivery address if different, nature of operation; Service Exécutant for B2G).
  * ☐ Compliance audit trail persisted (`compliance.reported` events).



Non-compliance has a price tag

€50 per missing e-invoice (capped at €15,000/year) and €500 per missing e-reporting transmission (capped at €15,000/year) — per the 2026 finance law. The DGFiP has signalled leniency for good-faith businesses in the first months, but “we hadn’t integrated yet” is not a defence after the SME deadline. 

## References

  * [France overview](<index.html>) — deadlines, required fields, PPF error codes, refus/rejet motifs.
  * [Lifecycle explorer](<lifecycle.html>) — all 14 statuses, interactive, with per-status code.
  * [API reference](<../../reference/index.html>) · [Webhook cookbook](<../../reference/webhooks.html>) · [Sandbox guide](<../../sandbox/index.html>).
  * [impots.gouv.fr](<https://www.impots.gouv.fr/facturation-electronique-et-plateformes-agreees>) — official reform portal & PA list; [external specifications](<https://www.impots.gouv.fr/specifications-externes-b2b>) (current v3.2).
  * [FNFE-MPE](<https://fnfe-mpe.org/>) — AFNOR XP Z12-012/013/014 annexes and Schematrons.
