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.
10000is EUR 100.00, not 10,000 euro. Divide by 100 withDecimal, 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. -
currencyis lowercase (eur). EN 16931 wantsEUR(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
|
eur → EUR. |
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 |
month → MON; 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"
}
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:
- 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”.
-
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)