Automation MCP Server Features Blog Pricing Contact
Automation

E-Invoicing in n8n: ZUGFeRD, XRechnung and Factur-X with One HTTP Request Node

n8n already runs the glue between your billing data and the places invoices have to go. The German and French mandates want structured documents that a PDF node cannot produce: ZUGFeRD and Factur-X hybrids, XRechnung XML with the Leitweg-ID. This guide adds them with the HTTP Request node you already know: credential once, JSON body with expressions, the validated file back as binary data for Drive, email or your ERP. It also covers the intake side, where received e-invoices are validated and read into rows, and the two n8n settings that keep a month-end batch from tripping over rate limits.

n8n is where a lot of invoicing automation already lives, and it would rather not move because a mandate arrived. Since January 2025 every German business must accept structured e-invoices and issuance becomes mandatory from 2027; France switches on in September 2026; Belgium already runs on Peppol. The documents these regimes want (ZUGFeRD and Factur-X hybrids, XRechnung XML, UBL) are not something a PDF node produces, and the usual way to close that gap has been to leave the workflow for a library and a deployment.

It does not need to be. The InvoiceXML REST API does the compliance work server-side, and n8n's HTTP Request node is a complete client for it: a Header Auth credential, a JSON body written with expressions, and the validated file comes back as binary data that every storage and email node understands. This guide builds the two workflows every team ends up needing, issuing and intake, and covers the handful of n8n settings that make them behave at month-end volumes. Nothing to install on the n8n side, nothing to redeploy when a format revs: the current FeRD, KoSIT and FNFE-MPE rule sets apply on every call, on their effective dates.


Which endpoint do you need?

Every workflow in this guide is the same HTTP Request node pointed at a different path. Pick by who you invoice:

You invoiceEndpointRequestResponse
German businesses (B2B)POST /v1/create/zugferdJSONZUGFeRD 2.3 PDF/A-3
German public buyers (B2G)POST /v1/create/xrechnungJSON, with the Leitweg-IDXRechnung 3.0 XML
French customers (September 2026 mandate)POST /v1/create/facturxJSONFactur-X PDF/A-3
Across Peppol (Belgium, Netherlands, Nordics)POST /v1/create/ublJSONPeppol BIS 3.0 UBL XML
You already have a branded PDF and need the hybridPOST /v1/embed/zugferd, /v1/embed/facturxmultipart: PDF plus XMLPDF/A-3 hybrid
You receive e-invoices and must check themPOST /v1/validate/{format}multipart fileValidation JSON
You receive e-invoices and want the dataPOST /v1/extract/jsonmultipart fileStructured JSON
Someone has to read an XRechnungPOST /v1/render/xrechnung/to/pdfXMLReadable PDF

The request body is identical across the create endpoints, so a workflow built for ZUGFeRD becomes a Factur-X or XRechnung workflow by editing one URL. Full reference: API documentation and the OpenAPI 3.1 schema.


The HTTP Request node, configured once

Three things to set, and the first one only ever once.

1. The credential. In n8n, open Credentials, add a Header Auth credential and fill in: Name Authorization, Value Bearer followed by a space and your InvoiceXML API key (from your account dashboard after signing up). Name the credential something like "InvoiceXML". The key now lives in n8n's encrypted credential store, not in the workflow JSON, so workflows can be exported, versioned and shared without carrying the secret.

2. The node. Add an HTTP Request node and set:

FieldValue
MethodPOST
URLhttps://api.invoicexml.com/v1/create/zugferd
AuthenticationGeneric Credential Type → Header Auth → your "InvoiceXML" credential
Send BodyOn, Body Content Type JSON, Specify Body Using JSON
Options → ResponseResponse Format File, Put Output in Field data

The JSON body type sets Content-Type: application/json for you. Response Format File is the setting people miss: without it n8n tries to parse the returned PDF as text. With it, the ZUGFeRD file arrives in the item's binary property data, which is exactly what the Google Drive, Gmail, Microsoft Outlook, Nextcloud, Dropbox and SFTP nodes read from.

3. The body. Paste the invoice document into the JSON field and replace the values with expressions from the previous node. The API accepts a nested document modelled on EN 16931, wrapped as { "invoice": { ... }, "options": { ... } }:

{
  "invoice": {
    "invoiceNumber": "{{ $json.invoiceNumber }}",
    "issueDate":     "{{ $json.issueDate }}",
    "dueDate":       "{{ $json.dueDate }}",
    "currency":      "EUR",
    "seller": {
      "name":          "Your Company GmbH",
      "vatIdentifier": "DE123456789",
      "postalAddress": { "line1": "Musterstraße 1", "postCode": "10115", "city": "Berlin", "country": "DE" }
    },
    "buyer": {
      "name":          "{{ $json.customerName }}",
      "vatIdentifier": "{{ $json.customerVatId }}",
      "postalAddress": {
        "line1":    "{{ $json.customerStreet }}",
        "postCode": "{{ $json.customerPostCode }}",
        "city":     "{{ $json.customerCity }}",
        "country":  "{{ $json.customerCountry }}"
      }
    },
    "paymentDetails": { "paymentAccountIdentifier": "DE02120300000000202051" },
    "lines": [
      {
        "quantity":       {{ $json.quantity }},
        "unitCode":       "HUR",
        "priceDetails":   { "netPrice": {{ $json.unitPrice }} },
        "vatInformation": { "rate": 19, "categoryCode": "S" },
        "item":           { "name": "{{ $json.description }}" }
      }
    ]
  },
  "options": { "language": "de", "brandColor": "#173a40" }
}

You do not send totals. The API computes each line's net amount, the VAT breakdown and the document totals from the lines, then validates the result against EN 16931 before returning the file; an HTTP 200 is a compliant invoice by definition. typeCode defaults to 380 (commercial invoice), the profile is set per format, and an IBAN makes the payment means a credit transfer. options.language de renders the PDF in German; brandColor and logoUrl brand it; pdfUrl replaces the template with your own PDF entirely (more on that below).


Workflow 1: ZUGFeRD from a trigger

The issuing workflow, five nodes long:

Google Sheets Trigger (row added or updated, status = "Ready")
  → Edit Fields (Set): normalise dates to yyyy-MM-dd, numbers to numbers
  → HTTP Request: POST /v1/create/zugferd, Response Format File
  → Google Drive: Upload file from binary field "data"
  → Gmail: Send message, attachment from binary field "data"
  → Google Sheets: update the row, status = "Issued"

The trigger can be anything that carries invoice data: a Webhook node your billing system POSTs to, a Postgres or MySQL trigger, a HubSpot deal reaching "Closed won", a Shopify or WooCommerce order, a Stripe payment. The Edit Fields step is worth keeping even when the source looks clean, because the API expects ISO dates and numeric quantities, and spreadsheet cells and webhook payloads are strings more often than they look. One node that sets issueDate to {{ $json.date.toDateTime().format('yyyy-MM-dd') }} and quantity to {{ Number($json.qty) }} removes a whole class of 400s.

For the Gmail node, set Options → Attachments → Attachment Field Name to data; for Google Drive, the Input Data Field Name is data. Name the file from the invoice number with an expression in the node's File Name field: {{ $('Edit Fields').item.json.invoiceNumber }}.pdf, reaching back to the earlier node because the HTTP Request item only carries the binary.

Validation failures behave the way automation wants. A rule violation returns HTTP 400 with the violated rules named in plain language and the field path that caused each, the item leaves through the node's error output (see below), and the message says which column to fix. Retrying unchanged data reproduces it, so there is nothing to babysit.


Line items from several rows

Real invoices have several lines, and in n8n those usually arrive as several items: a Lines sheet, an order's line records, an array in a webhook body. Two nodes turn them into the lines array.

First collect the rows of one invoice. A Google Sheets node reading the Lines tab with a filter on the invoice number does it, or an Aggregate node (All Item Data into a single list) after whatever produced the items. Then a Code node builds the whole request body, which keeps the HTTP Request node's JSON field to a single expression:

// Code node, mode "Run Once for All Items"
const header = $('Edit Fields').first().json;
const lines = $input.all().map(({ json: l }) => ({
  quantity:       Number(l.qty),
  unitCode:       l.unit || 'C62',          // C62 = piece, HUR = hour
  priceDetails:   { netPrice: Number(l.unitPrice) },
  vatInformation: { rate: Number(l.vatRate), categoryCode: l.vatRate > 0 ? 'S' : 'Z' },
  item:           { name: l.description }
}));

return [{ json: {
  invoice: {
    invoiceNumber: header.invoiceNumber,
    issueDate:     header.issueDate,
    dueDate:       header.dueDate,
    currency:      'EUR',
    seller:        { /* as above */ },
    buyer: {
      name:          header.customerName,
      vatIdentifier: header.customerVatId,
      postalAddress: { line1: header.customerStreet, postCode: header.customerPostCode, city: header.customerCity, country: header.customerCountry }
    },
    paymentDetails: { paymentAccountIdentifier: 'DE02120300000000202051' },
    lines
  },
  options: { language: 'de' }
} }];

In the HTTP Request node, Specify Body stays Using JSON and the JSON field becomes the expression {{ $json }}. The category codes matter to the rule set: S for standard VAT, AE for reverse charge, E exempt, K intra-Community, Z zero rated. The API groups lines by category and rate into the EN 16931 VAT breakdown and resolves exemption reason codes from the category, which is the part of the standard most hand-built workflows get wrong.


XRechnung and the Leitweg-ID

Invoicing a German federal, state or municipal buyer means XRechnung rather than ZUGFeRD: pure XML, validated by portals such as ZRE and OZG-RE against the KoSIT rules. Two changes to the workflow. The URL becomes /v1/create/xrechnung, and the body gains the buyer's Leitweg-ID:

"buyerReference": "{{ $json.leitwegId }}",
"seller": {
  "name": "Your Company GmbH",
  "electronicAddress": { "identifier": "[email protected]", "schemeId": "EM" },
  "contact": { "name": "Accounts", "phone": "+49 30 1234567", "email": "[email protected]" },
  ...
}

The Leitweg-ID is assigned by the public buyer during onboarding and appears on no source document, so it earns its own field on the customer record your trigger reads (as text: Leitweg-IDs carry leading zeros and dashes). Rule BR-DE-15 rejects the document without it, and the German rules also require a seller contact with name, phone and email, which is why the snippet adds them. The response is XML, so the storage node's file name ends in .xml and the Gmail attachment is the same binary field.

Pure XML gives an approver nothing to look at. For a review step, add a second HTTP Request node: POST /v1/render/xrechnung/to/pdf with Body Content Type Form-Data, a parameter of type n8n Binary File named file from field data, and Response Format File again. The result is a readable PDF that follows the official KoSIT visualization, suitable for an approval email or a Slack message before the XML goes to the portal. The full B2G treatment is in the XRechnung API toolkit.


Factur-X and your own PDF

For French customers the URL is /v1/create/facturx and nothing else changes; ZUGFeRD and Factur-X are the same standard with different branding in the PDF metadata. The same body produces a conformant Factur-X PDF/A-3 with the EN 16931 profile, validated before delivery.

Many teams already render a branded PDF elsewhere and want to keep its design. Two ways to do that from n8n. If the PDF is reachable by URL, set options.pdfUrl in the create body and the API embeds the generated XML into your document instead of rendering its template. If the PDF is a binary item in the workflow (it came out of a Gotenberg node, a DocuSign download, your ERP's print service), use the embed endpoint instead: POST /v1/embed/zugferd or /v1/embed/facturx with Body Content Type Form-Data and two parameters of type n8n Binary File: pdf for your PDF and xml for your CII XML. The XML is validated against the current rules before embedding, and the response is the PDF/A-3 hybrid with correct XMP metadata and your layout untouched. The embed guide covers that last-mile step in depth.


Workflow 2: validating received e-invoices

Reception is the half of the German mandate that is already live for everyone, and n8n's email triggers make it a short workflow:

Gmail Trigger or IMAP Email Trigger (Download Attachments: on)
  → HTTP Request: POST /v1/validate/zugferd, Form-Data, file = attachment_0
  → IF: {{ $json.valid }} is true
      → true:  create the bill in your accounting tool, archive the file
      → false: Slack or email to AP with {{ $json.errors.map(e => e.rule + ': ' + e.message).join('\n') }}

With Download Attachments on, the trigger puts each attachment in a binary field named attachment_0, attachment_1 and so on. In the HTTP Request node, set Body Content Type to Form-Data, add one body parameter with Parameter Type n8n Binary File, Name file, Input Data Field Name attachment_0. Response Format can stay on Autodetect, because validation returns JSON. Route PDF attachments to /v1/validate/zugferd or /v1/validate/facturx and XML attachments to /v1/validate/xrechnung, /v1/validate/ubl or /v1/validate/cii; a Switch node on the attachment's MIME type does the routing.

Validation always returns HTTP 200. The body carries valid, an errors array with the rule id, the layer that produced it (XSD, EN 16931 or the national overlay such as BR-DE) and a plain-language message, plus a field-by-field report. That is enough to tell a supplier precisely what to fix, and enough for an IF node to decide whether the document is allowed into your books.


Reading received e-invoices into your tools

The same trigger, pointed at /v1/extract/json instead, turns the attachment into structured invoice data: number, dates, seller, totals, lines, all as JSON fields the next node can map to a row in Google Sheets or Airtable, a bill in your accounting tool, or a record in your ERP. The read is deterministic for anything with structure (the embedded XML of a ZUGFeRD or Factur-X hybrid, or standalone XRechnung, CII or UBL): what the supplier declared is exactly what lands in the row.

For the suppliers still sending plain PDFs, /v1/parse/json reads them with AI, scans included, and returns the same JSON shape plus a confidence object. Treat the two accordingly: structured reads can post directly, AI reads deserve an IF node on the confidence score with a review path for the uncertain ones. /v1/extract/attachments rounds out the intake side by unpacking embedded supporting documents (delivery notes, timesheets) as a ZIP.


Errors, retries and batching

Three settings on the HTTP Request node decide how the workflow behaves when something is not a clean 200.

On Error. On the node's Settings tab, set On Error to Continue (using error output). A 400 from a rule violation now sends the item out of the node's second, red output with the API's response attached, instead of stopping the whole execution. Connect a Slack, email or Sheets node there to report the violated rules and the field paths. Keep validation failures on that path and nowhere else: retrying unchanged data reproduces them.

Retry On Fail. Same tab: enable it with Max Tries 3 and Wait Between Tries of at least 10000 ms. This is for the transient cases, a network blip or a 429 when a burst exceeds your plan's concurrent requests. A rejected request was never processed, so a retry can never create a duplicate document.

Batching. The HTTP Request node sends every item of a run at the same time, and a month-end run can be a few hundred items. Under Options, add Batching and set Items per Batch to the concurrent requests of your plan, with a Batch Interval of a second or so. The concurrent requests page has the per-plan numbers and the same advice for Zapier and Make.


n8n Cloud or self-hosted

Everything above is identical on n8n Cloud and on a self-hosted instance; the HTTP Request node and the Header Auth credential are core n8n, not a community package, so there is no node to install, pin or update when n8n releases. The one operational point is the credential: keep the API key in n8n's credential store (or inject it from an environment variable on self-hosted instances), never as a literal in a workflow's JSON.

On the API side, nothing from your workflow is retained: documents are processed in memory in Frankfurt, Germany, and discarded when the response is returned, with no AI training on your data. A self-hosted n8n in your own data centre plus this API therefore keeps invoice data (VAT numbers, IBANs, customer relationships) inside your infrastructure and a stateless EU processor, which is the setup most German data protection reviews want to see. Formats and rule sets move on regulators' calendars, and the endpoint is current with every FeRD, KoSIT and FNFE-MPE release on its effective date, so the workflow you build today does not age when XRechnung revs; the node keeps producing the current version.


Get started

Create a free InvoiceXML account → (100 free credits with the 30-day trial, no credit card), copy the API key into a Header Auth credential, drop an HTTP Request node into the workflow that already carries your invoice data, and the first validated ZUGFeRD can be in your Drive within the next ten minutes. If a rule finding needs a human explanation, integration assistance is part of the service: write to support and a person who knows the rule sets answers.

Related resources:


InvoiceXML is a REST API for European e-invoice compliance covering ZUGFeRD, XRechnung, Factur-X, Peppol UBL and CII, callable from any n8n workflow through the HTTP Request node. Stateless processing in Frankfurt, zero data retention, and the current official rule sets applied on every call.

Start free today

Ready to automate your invoices?

Validate, convert and embed compliant e-invoices through one API. Start your 30-day free trial. No credit card required.

GDPR Compliant No credit card required Setup in minutes
Peppol UBL
Factur-X
EN 16931
142 / 142 passed
Compliant
PDF/A-3 embedded