# Turning PDF invoices into ZUGFeRD/XRechnung — a 22-node workflow, and the 7 mistakes I made

**URL:** <https://community.n8n.io/t/turning-pdf-invoices-into-zugferd-xrechnung-a-22-node-workflow-and-the-7-mistakes-i-made/310891>\
**Category:** Built with n8n\
**Tags:** workflow-templates, read-pdf, extract-from-file, community-nodes\
**Created:** [September 2, 2026, 6:40pm UTC](https://community.n8n.io/t/turning-pdf-invoices-into-zugferd-xrechnung-a-22-node-workflow-and-the-7-mistakes-i-made/310891 "2026-09-02T18:40:46Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![pie](https://dub1.discourse-cdn.com/n8n/user_avatar/community.n8n.io/pie/32/188526_2.png) [@pie](https://community.n8n.io/u/pie)\
**Post date:** [September 2, 2026, 6:40pm UTC](https://community.n8n.io/t/turning-pdf-invoices-into-zugferd-xrechnung-a-22-node-workflow-and-the-7-mistakes-i-made/310891/1 "2026-09-02T18:40:46Z")

</div>

We had to solve a problem that a lot of German and EU companies will hit over  
the next 18 months: a mid-sized company writes its invoices in Word, and from  
2027 it legally has to send structured e-invoices — ZUGFeRD for businesses,  
XRechnung for public buyers. Replacing their invoicing process was not an  
option. So we put a workflow behind it instead.

**What it does**

IMAP inbox per department → split PDF attachments → archive the source PDF →  
extract text → parse it with rules → build an EN 16931 dataset and recompute  
the totals → validate → approve → generate the e-invoice → store → send.

22 nodes. Attached below as JSON with a dummy company and a generic parser.

**Why no LLM for the parsing**

The invoices come out of one Word template. Same layout every time, item table  
in the same place, totals block in the same place. For that, a rule-based  
parser is not the lesser option — it is the right one: same input, same output,  
every run. No per-call cost, no external service in the path of invoice data,  
and when it gets something wrong you can see why.

The honest trade-off: if the customer changes the template, the parser breaks.  
That is not a bug, it is the deal you make. An LLM earns its place on _incoming_  
invoices from many different senders — a different problem entirely.

**What is not on a PDF invoice**

Two things you will need and will not find in the document: the buyer’s email  
address and, for public-sector invoices, the Leitweg-ID. We solved it with a  
customer table as a mandatory input to the workflow. Same for bank details and  
the contact person per department. Plan for that table from the start — it is  
not an afterthought.

**Three validation stages**

1. Inside the workflow — totals must match within ±2 cents, required fields  
present, customer exists in the table.
2. `/validate` with the `valid` flag — object-level rules.
3. `/validate` with `results[]` per format — XSD and Schematron against  
XRechnung and ZUGFeRD.

**The 7 mistakes**

All of these were mine, and every single one only showed up on the first run  
with real documents:

1. `type: "creditnote"` — it is `credit_note`.
2. Left out `dueDate` because the PDF had no due date. It is mandatory;  
falling back to the invoice date is fine.
3. Forgot `countrySpecific.countryCode: "DE"` — without it the Leitweg-ID is  
not carried into the output.
4. Put the bank details in `paymentMeans`. They belong under  
`seller.bankAccount`.
5. No contact person. BR-DE-2 requires `seller.contact`.
6. Bank details alone are not enough — BR-DE-1 also wants  
`paymentMethods: [{ type: "bank_transfer" }]`.
7. Credit note with negative line items. The sign is carried by the document  
type, the amounts stay positive (BR-27).

**Two things that cost me an hour each**

_One PDF, two invoices._ Extract text **per page** , not per document, and send  
each page to the parser separately. Empty pages do no harm.

_Duplicate detection._ Build your own fingerprint over the invoice data with a  
stable key order. Do not use a byte-level hash of the request — it changes  
between calls even for identical content.

**What this workflow does not do**

- No OCR. Scanned PDFs without a text layer will not work.
- Template changes break the parser (see above).
- No duplicate protection across separate runs.
- No human approval step. It is easy to add, it is just not in there.

**Nodes used**

IMAP Email Trigger, Code, HTTP Request, Read/Write Files From Disk, Switch,  
IF, and the community node `n8n-nodes-invoice-api-xhub` (MIT, currently 1.2.1)  
for building and validating the e-invoice.

**Disclosure:** I am one of the makers of that community node. The workflow  
itself is generic — the parsing, the page splitting and the fingerprint work  
with any e-invoicing backend you prefer.

Happy to answer questions about the parsing or the EN 16931 side of it. And if  
anyone has solved the OCR case for scanned invoices in n8n, I would like to  
hear how.
