ZUGFeRD-Rechnung aus einem Webformular: n8n ohne eigene PDF
Der erste n8n-Artikel hier hatte eine Lücke: Der Workflow brauchte eine fertige Rechnungs-PDF, und wer nur Daten hat, musste sich die PDF vorher selbst bauen. Seit Version 0.4.0 des finisma-Nodes fällt dieser Schritt weg. Dieser Artikel zeigt den kleinsten Workflow, der damit möglich ist: ein Webformular, fünf Nodes, eine gültige ZUGFeRD-Rechnung als Download. Sie können ihn importieren und in zehn Minuten laufen lassen.
Das Video: in drei Minuten vom Formular zur Rechnung
Das Video zeigt alles einmal durch: Node installieren, Workflow aufbauen, API-Key anlegen, Formular absenden, Ergebnis im ZUGFeRD-Viewer prüfen. Der Text hier ergänzt, was im Video keinen Platz hatte: den Code-Node im Detail, die zwei Fehler, die uns beim Festschreiben der Datei aufgefallen sind, und die Stellen, an denen Sie den Workflow anpassen werden.
1. Was Sie brauchen
- Eine selbst gehostete n8n-Instanz mit aktivierten Community Nodes. In n8n Cloud geht es erst, wenn n8n das Paket verifiziert hat; dort bleibt bis dahin der HTTP-Request-Weg aus dem Artikel E-Rechnungen automatisch mit n8n erzeugen.
- Den Community-Node
n8n-nodes-zugferd-finismaab Version 0.4.0, installiert über Settings → Community Nodes. Die n8n-Dokumentation beschreibt die drei Klicks. - Ein finisma-Konto mit API-Key. Das Konto ist kostenlos, der Key entsteht unter finisma.de/konto. Mit dem kostenlosen Konto erzeugen Sie 5 Rechnungen pro Tag, jede trägt das Prüfsiegel als Wasserzeichen. Ohne Wasserzeichen und ohne Tageslimit geht es mit Credits (10 € für 25 Rechnungen) oder im Abo ab 8 € im Monat. Die Preisseite hat die Tabelle.
Wer die Pflicht im Nacken hat: Ab 2027 beginnt die Pflicht, E-Rechnungen auch auszustellen, zuerst für Unternehmen über 800.000 € Vorjahresumsatz, ab 2028 für alle. Die Fristen stehen im Artikel E-Rechnungspflicht ab 2027. Ein Formular wie dieses ist für kleine Teams ohne Rechnungsprogramm oft der kürzeste Weg dahin.
2. Workflow importieren und starten
- Community-Node installieren. In n8n unter Settings → Community Nodes das Paket n8n-nodes-zugferd-finisma installieren.
- Workflow importieren. Die Datei rechnung-aus-formular.json herunterladen und im n8n-Editor über das Menü „Import from File“ laden.
- Zugangsdaten hinterlegen. Im Node „Create invoice“ einen finisma-API-Key als Credential anlegen. Den Key erzeugen Sie unter finisma.de/konto.
- Verkäufer eintragen. Im Code-Node die Konstanten VERKAEUFER, IBAN und ZAHLUNGSZIEL_TAGE auf die eigenen Werte setzen.
- Veröffentlichen und testen. Den Workflow veröffentlichen, die Produktions-URL des Webhooks öffnen, das Formular absenden. Der Download startet sofort.
Die Formular-URL ist die Produktions-URL des Webhook-Nodes, also https://ihre-instanz/webhook/rechnung. Die Test-URL funktioniert nur, solange der Editor auf „Listen for test event“ wartet, und sie beantwortet genau eine Anfrage. Für das Formular mit GET und POST reicht das nicht, deshalb erst veröffentlichen, dann öffnen.
3. Die fünf Nodes
Die Idee ist alt und hält sich, weil sie so einfach ist: Ein Webhook beantwortet GET mit einer Seite und POST mit dem Ergebnis. Ein Node liefert die Seite, einer rechnet, einer ruft finisma, einer antwortet.
| Node | Typ | Aufgabe |
|---|---|---|
| Webhook | Webhook | Ein Pfad, zwei Methoden: GET liefert das Formular, POST nimmt es entgegen. Antwort kommt aus einem Respond-Node. |
| show form | Respond to Webhook | Schickt das HTML-Formular als text/html zurück. |
| create invoice Json from data | Code | Formularfelder in die Rechnungsdaten übersetzen, Summen und Steuergruppen rechnen, Verkäufer und IBAN ergänzen. |
| Create invoice | finisma | Quelle „Generate From Invoice Data“: erzeugt die ZUGFeRD-PDF aus den Daten. Fehler gehen auf den zweiten Ausgang. |
| download PDF | Respond to Webhook | Gibt die Binärdatei mit Content-Disposition: attachment zurück, der Browser lädt sie herunter. |
| show error | Respond to Webhook | Zeigt die Fehlermeldung der API als Seite mit Status 422. |
Der Webhook-Node hat „Allow Multiple HTTP Methods“ an, Pfad rechnung, Antwort „Using Respond to Webhook Node“. Er bekommt damit zwei Ausgänge: GET geht an „show form“, POST an den Code-Node. Das Formular selbst ist ein schlichtes HTML mit <form method="post"> ohne Ziel, es postet also auf dieselbe URL, von der es kam.
Im finisma-Node sind drei Dinge gesetzt: Quelle Generate From Invoice Data, Invoice Data (JSON) als Ausdruck {{ JSON.stringify($json.invoice) }} und unter Settings „On Error“ auf Continue (using error output). Der zweite Ausgang führt zur Fehlerseite. Ohne diese Einstellung bricht der Workflow ab, und der Browser bekommt nach einer Weile einen leeren Fehler von n8n statt der Meldung der API.
4. Der Code-Node: aus Formularfeldern werden Rechnungsdaten
Der Code-Node ist das einzige Stück, das Sie wirklich lesen sollten. Oben stehen die Konstanten, die in jede Rechnung wandern. Darunter kommen die Formularfelder aus $input.first().json.body:
// Standardwerte: hier den eigenen Verkäufer und die Bankverbindung eintragen.
const VERKAEUFER = {
name: 'Muster GmbH',
vatId: 'DE123456789',
address: { line1: 'Musterstraße 1', postalCode: '20095', city: 'Hamburg', countryCode: 'DE' },
};
const IBAN = 'DE02120300000000202051';
const ZAHLUNGSZIEL_TAGE = 14;
// Formularfelder in die Rechnungsdaten übersetzen.
const f = $input.first().json.body ?? {};
const zahl = (v) => Number(String(v ?? '').trim().replace(/\./g, '').replace(',', '.'));
const positionen = [1, 2, 3]
.filter((i) => String(f['p' + i + '_text'] ?? '').trim() !== '')
.map((i) => ({
name: String(f['p' + i + '_text']).trim(),
quantity: zahl(f['p' + i + '_menge'] || '1'),
unitPriceNet: zahl(f['p' + i + '_preis']),
vatRatePercent: zahl(f['p' + i + '_ust'] || '19'),
}));Die Funktion zahl macht aus „120,00“ die Zahl 120 und aus „1.250,50“ die 1250.5. Wer Kommazahlen aus einem deutschen Formular ungeprüft nach Number() gibt, bekommt NaN, und finisma lehnt die Rechnung mit einem Schemafehler ab. Leere Positionszeilen fallen weg, sodass das Formular drei Zeilen anbieten kann und eine reicht.
Danach rechnet der Node, was EN 16931 vorrechnet haben will: Zeilenbeträge, eine Steuergruppe je Satz mit Basis und Steuer, Netto, Steuer, Brutto. Alles auf zwei Stellen gerundet, und zwar an derselben Stelle, an der finisma später nachrechnet.
const gruppen = {};
for (const l of lines) {
const g = (gruppen[l.vatRatePercent] ??= {
ratePercent: l.vatRatePercent, basisAmount: 0,
categoryCode: l.vatRatePercent > 0 ? 'S' : 'Z',
});
g.basisAmount = r2(g.basisAmount + l.lineNetAmount);
}
const vatBreakdown = Object.values(gruppen)
.map((g) => ({ ...g, taxAmount: r2((g.basisAmount * g.ratePercent) / 100) }));
const totalNet = r2(lines.reduce((s, l) => s + l.lineNetAmount, 0));
const totalVat = r2(vatBreakdown.reduce((s, g) => s + g.taxAmount, 0));Bei 0 % setzt der Node die Kategorie Z (Nullsatz). Für Reverse Charge oder Kleinunternehmer wäre das falsch, dort gehören AE beziehungsweise E mit Befreiungsgrund hinein; der Artikel Reverse Charge in der E-Rechnung erklärt das. Für ein Formular, das Beratung und Bücher abrechnet, reicht die Unterscheidung S und Z.
So sehen die Daten aus, die der Node an finisma schickt, mit den Vorgabewerten des Formulars (Beratung 2 × 120 € zu 19 %, ein Fachbuch zu 7 %):
{
"invoiceNumber": "RE-20261019-6210",
"issueDate": "2026-10-19",
"dueDate": "2026-11-02",
"seller": { "name": "Muster GmbH", "vatId": "DE123456789",
"address": { "line1": "Musterstraße 1", "postalCode": "20095", "city": "Hamburg", "countryCode": "DE" } },
"buyer": { "name": "Kunde AG",
"address": { "line1": "Allee 2", "postalCode": "10115", "city": "Berlin", "countryCode": "DE" } },
"payment": { "iban": "DE02120300000000202051", "reference": "RE-20261019-6210" },
"lines": [
{ "name": "Beratung", "quantity": 2, "unitCode": "C62", "unitPriceNet": 120, "vatRatePercent": 19, "lineNetAmount": 240 },
{ "name": "Fachbuch", "quantity": 1, "unitCode": "C62", "unitPriceNet": 39.9, "vatRatePercent": 7, "lineNetAmount": 39.9 }
],
"vatBreakdown": [
{ "ratePercent": 19, "basisAmount": 240, "categoryCode": "S", "taxAmount": 45.6 },
{ "ratePercent": 7, "basisAmount": 39.9, "categoryCode": "S", "taxAmount": 2.79 }
],
"totalNet": 279.9,
"totalVat": 48.39,
"totalGross": 328.29
}Mengeneinheit ist C62, das Stück nach UN/ECE. Wer Stunden abrechnet, trägt HUR ein; welche Codes es gibt, steht im Artikel Mengeneinheiten in der E-Rechnung. Die vollständige Feldliste ist das Schema Invoice in der REST-Referenz.
5. Zwei Fehler aus der Video-Fassung
Der Workflow im Video lief, und wir hätten ihn so veröffentlicht. Beim Festschreiben der Datei sind dann zwei Dinge aufgefallen, die im Video nicht zu sehen waren.
Der Download hatte keinen Dateinamen. Der Node „download PDF“ baut den Header Content-Disposition aus $binary.zugferd.fileName. Der finisma-Node legt die PDF aber in das Binärfeld data, so steht es als Vorgabe im Parameter „Output Binary Field“. n8n meldet in so einem Ausdruck keinen Fehler: Der ganze Ausdruck wird null, der Header fällt weg, und der Browser bekommt die PDF ohne Namen. Wir haben das mit einem Testworkflow über die n8n-CLI nachgestellt (n8n 2.32.7): Das Feld mit $binary.zugferd kam als null zurück, das mit $binary.data als „attachment; filename=…“. In der Datei zum Download heißt der Ausdruck jetzt $binary.data.fileName.
Der Dateiname war fest „invoice“. Im finisma-Node stand unter „Output File Name“ das Wort invoice, damit hieß jede Rechnung gleich. Der Code-Node liefert bereits filename mit der Rechnungsnummer, also steht dort jetzt der Ausdruck {{ $json.filename }}. Lässt man das Feld leer, nimmt der Node selbst die Rechnungsnummer.
Ein dritter Punkt ist kein Fehler, hat uns aber die Fehlerseite vorgeführt: Beim Nachtest lag in der Instanz ein widerrufener API-Key. finisma antwortet darauf mit 401 invalid_key, der Fehlerausgang greift, und der Browser zeigt die Meldung „Ungültiges oder widerrufenes Credential“ statt einer leeren Seite. Genau dafür ist der zweite Ausgang da.
6. Was Sie anpassen werden
Formular absichern
Die Webhook-URL ist öffentlich, sobald der Workflow aktiv ist. Wer sie kennt, kann Rechnungen auf Ihr Kontingent erzeugen. Für den Einsatz im Team gehört im Webhook-Node unter „Authentication“ mindestens eine Basic Auth hinein; n8n fragt dann vor dem Formular nach Name und Passwort. Unsere Meinung: Ohne diesen Schritt bleibt der Workflow ein Demo. Wenn das Formular nur im Intranet erreichbar ist, kann man es sich sparen.
Logo und Sprache
Ein Node „Read/Write Files from Disk“ vor dem finisma-Node lädt das Logo in ein Binärfeld, etwa logo. Im finisma-Node tragen Sie den Namen unter „Logo Binary Field“ ein. PNG, JPEG oder SVG bis 500 KB, es landet rechts oben im Kopf. „Language“ schaltet die Beschriftungen auf Englisch, die Daten bleiben, wie sie sind.
Fortlaufende Rechnungsnummer
Die Nummer aus Datum und Uhrzeit ist eindeutig, aber nicht fortlaufend. Für den Alltag liest der Code-Node die nächste Nummer aus einer n8n Data Table oder einem Google Sheet und schreibt sie nach dem Erzeugen zurück. Das sind zwei Nodes mehr, am Formular ändert sich nichts.
Versand statt Download
Statt „download PDF“ hängen Sie einen E-Mail-Node an, der das Binärfeld data als Anhang verschickt, und antworten dem Browser mit einer kurzen Bestätigung. Für Kunden, die ihre Rechnungen über Peppol bekommen wollen, prüft der Node mit „Check Peppol Participant“ vorher, ob die Adresse existiert.
7. Ergebnis prüfen
finisma prüft jede erzeugte Datei vor der Auslieferung gegen PDF/A-3 und die Geschäftsregeln der EN 16931. Fällt sie durch, kommt ein 502 mit den Befunden statt einer halbgaren Rechnung, und ein verbrauchter Credit wird zurückgebucht. Wer das selbst sehen will, zieht die heruntergeladene Datei in den ZUGFeRD-Viewer: Er zeigt das eingebettete XML, das Profil (EN 16931) und das Prüfergebnis. So endet auch das Video.
Und wenn Sie doch eine fertige PDF aus Word oder dem alten Rechnungsprogramm haben, brauchen Sie keinen Workflow: Auf der Startseite können Sie kostenlos PDF in E-Rechnung umwandeln, ohne Anmeldung.
examples/. Änderungsvorschläge nehmen wir dort als Pull Request.8. Häufige Fragen
Brauche ich eine fertige Rechnungs-PDF?+
Nein. Seit Version 0.4.0 hat der finisma-Node die Quelle „Generate From Invoice Data“. finisma setzt die Rechnung dann aus den Daten auf A4, mit Kopf, beiden Parteien, Positionen, Summen, Steueraufschlüsselung und Zahlungsangaben, und bettet das CII-XML ein. Wer eine eigene PDF hat, nutzt weiterhin die Quelle „PDF From Binary Field“.
Läuft der Workflow in n8n Cloud?+
Erst, wenn n8n das Paket als verifizierten Community-Node freigegeben hat. Bis dahin braucht es eine selbst gehostete Instanz mit aktivierten Community Nodes. Wer heute in n8n Cloud arbeitet, nimmt den HTTP-Request-Weg aus dem Artikel „E-Rechnungen automatisch mit n8n erzeugen“ und lässt den pdf-Part weg.
Was kostet das Erzeugen?+
Mit einem kostenlosen Konto und API-Key sind es 5 Rechnungen pro Tag, jede mit dem finisma-Prüfsiegel als Wasserzeichen. Ohne Wasserzeichen und ohne Tageslimit läuft es mit Credits (10 € für 25 Rechnungen) oder im Abo ab 8 € im Monat zzgl. MwSt. Das Prüfen einer fertigen Rechnung ist kostenlos.
Wie bekomme ich mein Logo auf die Rechnung?+
Ein Node „Read/Write Files from Disk“ lädt die Datei in ein Binärfeld, zum Beispiel logo. Im finisma-Node tragen Sie diesen Feldnamen unter „Logo Binary Field“ ein. Erlaubt sind PNG, JPEG und SVG bis 500 KB, das Logo steht rechts oben, höchstens 22 mm hoch und 70 mm breit.
Ist das Formular öffentlich erreichbar?+
Ja, sobald der Workflow aktiv ist, antwortet die Webhook-URL jedem, der sie kennt. Für den Einsatz im Team stellen Sie im Webhook-Node unter Authentication eine Basic Auth oder einen Header-Schlüssel ein. Jede Rechnung, die über das Formular entsteht, zählt gegen Ihr Kontingent.
Woher kommt die Rechnungsnummer?+
Aus dem Formularfeld, wenn es gefüllt ist. Sonst baut der Code-Node eine Nummer aus dem Datum und vier Ziffern der Uhrzeit. Für eine fortlaufende Nummer, wie § 14 UStG sie verlangt, gehört davor ein Zähler, etwa in einer n8n Data Table oder einer Tabelle, aus der der Code-Node die nächste Nummer liest.
Quellen und Einordnung
Node-Paket n8n-nodes-zugferd-finisma (Version 0.4.0, MIT-Lizenz) mit Quelltext und Changelog auf GitHub; n8n-Dokumentation zu Community Nodes; finisma REST-Referenz und OpenAPI-Spezifikation für das Schema Invoice. Die Beträge der Musterrechnung stammen aus einer Ausführung des Workflows am 19. Oktober 2026. Preise Stand Oktober 2026.