DEV Community

Thor
Thor

Posted on

Stripe Invoice JSON EN 16931 BT-* (Germany 2026)

Stripe Invoicing stores an invoice record and exposes it on the API and on webhooks. It does not emit a legally compliant German e-invoice file. Stripe’s own e-invoicing docs say so: you either install a Marketplace app (Billit is the named partner) or you build a webhook that maps the Invoice object into EN 16931 XML yourself. This article is that map — Stripe JSON paths to CEN business terms (BT-*) and the German CIUS (XRechnung 3.0.2). You can implement it in any language. No hosted e-invoicing API is required for the mapping itself.

This is not tax advice. Never invent a USt-IdNr / VAT ID.

Spec pin (31 Aug 2026)

Pin Value
Semantic model EN 16931-1:2017 (the 2026 recast is not the production CIUS yet)
German CIUS XRechnung 3.0.2, KoSIT validator configuration 2026-01-31
CustomizationID BT-24 urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 — yes, _3.0, not _3.0.2. The old 2.x form xoev-de:kosit:standard:xrechnung_ is wrong (BR-DE-21).
ProfileID BT-23 (UBL) urn:fdc:peppol.eu:2017:poacc:billing:01:1.0
Syntax A UBL 2.1 Invoice (what KoSIT’s XRechnung Invoice scenario matches)
Syntax B UN/CEFACT CII, ZUGFeRD 2.5.2 / Factur-X 1.09.2, EN 16931 profile
CII guideline BT-24 urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:en16931 (1p0 is the Factur-X family token, not the 1.09.2 package version)
XRechnung 4.0 Preview only as of 31 Aug 2026; no production bundle, CEN syntax bindings still outstanding

ZUGFeRD 2.5.2 is FeRD’s 4 August 2026 corrigendum of 2.5 (Factur-X 1.09.2), current download, effective 1 September 2026. It does not change the BMF-accepted format family: ZUGFeRD from 2.0.1 onward in the EN 16931 profile (MINIMUM and BASIC-WL are out). ZUGFeRD 2.x is CII XML. The usual exchange form is a PDF/A-3 hybrid with that XML embedded; the structured XML is the e-invoice under § 14 UStG.

Invoice type BT-3 is 380 (commercial invoice) for this map. Credit notes are a different object (Stripe CreditNote / type 381) and are out of scope here.

Receive vs issue (BMF, not a vibe)

Two different duties. Do not collapse them.

Receive (already live). Since 1 January 2025 every domestic German business — including Kleinunternehmer — must be able to receive e-invoices. There is no receive exception (BMF FAQ). An e-mail inbox is enough. The statute does not mandate Peppol, a portal, or a specific viewer. There is no separate statutory “must-read” duty; visualization is practical, not the mandate. The structured XML original must be kept unaltered (§ 14b UStG / GoBD). A paper printout is not the original. For a hybrid ZUGFeRD PDF, keeping the structured part is sufficient unless the PDF carries extra tax-relevant data.

Issue (2026 is still the transition). Through 31 December 2026 any issuer may still send a “sonstige Rechnung”. Paper is always allowed in that window. An unstructured PDF still needs the recipient’s consent. From 1 January 2027, issuers whose 2026 (prior-year) turnover exceeded EUR 800,000 must issue an EN 16931 e-invoice (non-EN-16931 EDI may run through 31 December 2027). From 1 January 2028 remaining in-scope B2B issuers. Kleinunternehmer stay exempt from issuing under § 34a UStDV; they are not exempt from receiving.

Transport for domestic B2B is not prescribed: e-mail, interface, shared storage, USB stick, or a portal download all count. Peppol is a B2G / reachability regime, not a UStG requirement.

Units Stripe will trip you on

  • Amounts are integer cents. 10000 is EUR 100.00, not 10,000 euro. Divide by 100 with Decimal, not float.
  • Dates are Unix seconds unless noted. status_transitions.finalized_at / created / due_date / period_start → ISO date (YYYY-MM-DD) for BT-2 / BT-9 / BT-73 / BT-74.
  • currency is lowercase (eur). EN 16931 wants EUR (BT-5).
  • Seller legal identity is not on a Stripe Invoice. Overlay it. Never invent a USt-IdNr.
  • Prefer tax_behavior: exclusive. Inclusive Stripe tax must be unpicked before you fill BT-146 / BT-131; EN 16931 line amounts are net.

A webhook often wraps the Invoice as event.data.object. Unwrap that first.

The field map

Use this table even without a CLI.

Stripe field Canonical / BT-* Notes
number (fallback id) invoice_number BT-1 Rechnungsnummer. Use Stripe id (in_…) only if number is still empty (draft).
status_transitions.finalized_at else created issue_date BT-2 Unix → ISO date.
(fixed 380) type_code BT-3 Commercial invoice.
currency currency BT-5 eurEUR.
due_date due_date BT-9 Unix → ISO.
metadata.leitweg_id or metadata.buyer_reference buyer_reference BT-10 BR-DE-15 mandatory on every XRechnung, not only B2G. B2G: the authority’s Leitweg-ID (do not guess). B2B: buyer reference / PO number.
description note BT-22 Free text.
period_start / period_end invoicing period BT-73 / BT-74 Leistungszeitraum. Also emit per-line BG-26 from lines.data[].period when present.
seller.name or metadata.seller_name or account_name seller name BT-27 Overlay. Stripe account_name is weak — prefer seller.
seller.street / city / postal_code / country seller address BG-5 / BT-35…BT-40 Overlay.
seller.vat_id / metadata.seller_vat BT-31 USt-IdNr Never invented. Empty stays empty; a strict check fails BR-DE-16 if the VAT category requires it.
seller.tax_number BT-32 Steuernummer Alternative to BT-31 for some DE cases.
seller.email / phone / contact_name BT-43 / BT-42 / BT-41 BR-DE-5 / 6 / 7 — XRechnung expects seller contact.
seller.email as EndpointID scheme EM BT-34 Seller electronic address (BR-DE-18).
seller.iban / bic / account_name payment BG-16 / BT-84 / BT-86 BR-DE-1. Means code 58 (SEPA credit transfer). Fixture IBANs are labelled EXAMPLE.
customer_name buyer name BT-44
customer_address.line1 / city / postal_code / country / state buyer address BG-8 line1 → street; state → region.
customer_email buyer electronic address BT-49 scheme EM.
customer_tax_ids[] where type=eu_vat buyer VAT BT-48 Also accepts Customer tax_ids.data. Required for AE / K.
lines.data[].description line name BT-153
lines.data[].quantity BT-129
lines.data[].price.recurring.interval unit BT-130 monthMON; else C62 (one piece).
lines.data[].price.unit_amount / 100 net unit price BT-146 Exclusive tax.
lines.data[].amount / 100 line net BT-131
lines.data[].tax_amounts[].tax_rate.percentage or default_tax_rates[].percentage line rate BT-152 / document rate BT-119 DE typically 19 or 7.
metadata.vat_category line VAT category S Z E AE K G O. Default S. You choose the category; the mapper must not.
metadata.exemption_reason / exemption_reason_code BT-120 / BT-121 Required for AE / K / G.
metadata.kleinunternehmer flag Forces category E, tax 0, § 19 note. Does not invent a VAT ID. Issuing stays exempt.
subtotal / tax / total cross-check only Recompute canonical totals with Decimal, 2 places, ROUND_HALF_UP. Do not trust float.

BT-24, BT-23, payment means code 58, and type code 380 do not exist on the Stripe object. They are constants of the CIUS / syntax you emit, not Invoice fields.

Seller overlay

Stripe will not give you BT-31, BT-32, BT-41–43, BT-34, or BG-16. Merge a seller object from your own master data (CLI flag, env file, or a top-level seller key you add after the webhook — never from a guessed string).

{
  "name": "Beispiel GmbH",
  "street": "Musterstraße 1",
  "city": "Berlin",
  "postal_code": "10115",
  "country": "DE",
  "vat_id": "DE123456789",
  "email": "rechnung@beispiel.example",
  "phone": "+49-30-000000-0",
  "contact_name": "Ina Beispiel",
  "iban": "DE89370400440532013000",
  "bic": "COBADEFFXXX"
}
Enter fullscreen mode Exit fullscreen mode

DE123456789 and the IBAN above are textbook examples, not live identifiers. Put your values. If you omit vat_id, mapping can still run; a BR-DE-16 check must fail rather than invent DE000000000, TODO, or XXX. Reject those placeholders on sight.

Leitweg-ID (public-sector buyer routing id, Grobadressat-Feinadressat-Prüfziffer) comes from the authority, not from customer_address. Put it in metadata.leitweg_id. You cannot derive it from Stripe customer fields.

What Stripe will never give you

Seller USt-IdNr, Steuernummer, IBAN, Leitweg-ID, VAT category K / AE / G with VATEX codes, Kleinunternehmer status. Those are master data. The map exists so a webhook can merge them locally without sending names, VAT IDs, and line items to a hosted e-invoicing API.

Also not on the Invoice: Handelsregister number, correct seller postal address (Stripe account_name is a display string), and any guarantee that customer_tax_ids is populated. If you need BT-48, fetch Customer tax_ids (type eu_vat) and merge it.

VAT categories and rounding

Commercial rounding: Decimal, two fractional digits, ROUND_HALF_UP. Per tax group: tax = round(taxable amount × rate / 100). Net + tax = gross (BT-109 + BT-110 = BT-112). Do not copy Stripe subtotal / tax / total into those BTs as source of truth — they are a cross-check against a float-ish JSON number.

Code Meaning (DE) Typical rate BT-120 / BT-121
S Standard / reduced 19 or 7
Z Zero rate 0 reason recommended
E Exempt 0 reason text required (Kleinunternehmer § 19)
AE Reverse charge 0 VATEX-EU-AE + text; tax amount 0
K Intra-community supply 0 VATEX-EU-IC; delivery country + buyer VAT ID
G Export, third country 0 VATEX-EU-G
O Outside scope 0 VATEX-EU-O

Worked S-19: net 100.00 + tax 19.00 = gross 119.00. Worked S-7: 10 × 29.00 = 290.00 net; tax 20.30; gross 310.30.

Wrong category is wrong tax. A mapper that “picks S because Germany” on an intra-community line is not doing you a favour. For AE / K / G, tax must be 0 (not 19% with a note). Buyer VAT ID (BT-48) is required for AE / K.

BR-DE rules this map is designed around

XRechnung is EN 16931 plus German business rules. The ones Stripe-shaped payloads miss first:

Code BT Typical miss
BR-02 BT-1 Draft Invoice with empty number and you forgot the id fallback.
BR-DE-15 BT-10 No metadata.leitweg_id / buyer_reference. Mandatory on every XRechnung.
BR-DE-1 BG-16 No seller IBAN. Payment means code 58.
BR-DE-5/6/7 BT-41/42/43 No contact_name / phone / email on the overlay.
BR-DE-16 BT-31 Empty seller VAT ID / tax number while category is S/Z/E/AE/K/G.
BR-DE-18 BT-34 No seller EndpointID; use email + scheme EM.
BR-DE-21 BT-24 CustomizationID not exactly the _3.0 URN above.
BR-AE-02 BT-48 Reverse charge / intra-community without buyer VAT ID.
BR-AE-10 BT-120 AE/K/G without exemption reason / VATEX code.
BR-IC-12 BT-80 Intra-community without delivery country.
BR-CO-10 BT-106 Sum of lines ≠ sum of BT-131 (float cents).
BR-DE-TMP-32 BT-72 / BG-14 / BG-26 Information only: no actual delivery date and no invoicing period. Set period_start / period_end.

Local structural checks are not the official KoSIT validator. Before you send to ZRE / OZG-RE / a buyer portal, run KoSIT on your file with your identity fields.

Two files, two BT-24 values

Same canonical invoice, two syntaxes:

  1. XRechnung 3.0.2 UBL Invoice — BT-24 is the XRechnung 3.0 CIUS URN. This is what German public buyers and many B2B recipients mean by “XRechnung”.
  2. ZUGFeRD 2.5.2 / Factur-X 1.09.2 CII — BT-24 is the Factur-X EN 16931 guideline URN. KoSIT’s XRechnung scenarios will not match that file (noScenarioMatched). That is expected: it is not an XRechnung CIUS document. BMF still lists ZUGFeRD ≥ 2.0.1 EN 16931 as an accepted e-invoice format. Do not relabel a Factur-X file as XRechnung to force a KoSIT pass.

You can emit CII XML without writing a PDF/A-3 hybrid. That XML is still an e-invoice under § 14 UStG; the hybrid PDF is a convenience wrapper. A received hybrid can be read by extracting the embedded factur-x.xml / zugferd-invoice.xml.

Kleinunternehmer

metadata.kleinunternehmer is a receive/issue-policy flag, not a VAT ID generator. If set: category E, tax 0, a § 19 note. It must not fill BT-31 with a dummy. Issuing structured EN 16931 remains exempt under § 34a UStDV through the current mandate. Receiving is already mandatory.

Validation honesty

Local arithmetic and a handful of BR-DE checks are not the official validator. Before you send a file to ZRE, OZG-RE, or a buyer, run the KoSIT validator on your XML, with your identity fields filled.

Production pin as of 31 August 2026: KoSIT validator 1.6.3 plus XRechnung configuration 2026-01-31 (XRechnung 3.0.2). The configuration bundle may still mention engine 1.6.0 in its notes; 1.6.3 is the current engine tag and runs the same 3.0.2 scenarios.

KoSIT’s XRechnung Invoice scenario matches UBL with the _3.0 CustomizationID. A ZUGFeRD/Factur-X CII file will typically return noScenarioMatched there. That is expected: it is not an XRechnung CIUS document. Do not relabel CII as XRechnung to force a pass. Do not treat a green local checksum as “KoSIT-valid” or “certified.”

Sources

  • Stripe: Invoicing e-invoicing“doesn’t generate or transmit legally compliant e-invoice files on its own.” Germany page: e-invoice in Germany.
  • XRechnung 3.0.2 / KoSIT bundle 2026-01-31: xeinkauf.de versions, Winter 2025/26 bugfix, English summary 3.0.2. Validator: KoSIT engine 1.6.3 + configuration tag v2026-01-31.
  • ZUGFeRD 2.5.2 / Factur-X 1.09.2: FeRD 4 Aug 2026; FNFE-MPE (identical standard). BMF Q7 still accepts ZUGFeRD ≥ 2.0.1 EN 16931.
  • BMF FAQ E-Rechnung (stand 23.03.2026): receive since 1 Jan 2025, e-mail inbox enough (Q8/Q12), no extra “read” duty, issue calendar Q11, Kleinunternehmer issue-exempt § 34a UStDV (Q4), structured part to archive (Q13). GoBD 2nd amendment 14 Jul 2025.

Further resource (optional)

This article is the map. If you want a packaged local Python CLI that implements it — UBL + CII emit, Stripe overlay, hash-sidecar archive, no hosted API — one implementation is E-Invoice Kit 2026. You do not need it to use the table above. Not tax or legal advice. Never invent a VAT ID.

Top comments (0)