Automation MCP Server Features Blog Pricing Contact
Automation

E-Invoicing in Microsoft Power Automate: ZUGFeRD, XRechnung, Factur-X and UBL from Dynamics 365

A single HTTP action in Power Automate is all it takes to transform invoice data from Dynamics 365 into a validated, mandate-ready e-invoice. This guide covers format selection, full flow structure, Dynamics 365 field mapping, and format-specific configuration for Germany, France, Belgium, and the Peppol network.

If your business runs on Microsoft 365, Dynamics 365, or Power Platform, e-invoice compliance does not require replacing your existing stack. A single HTTP action in Power Automate is all it takes to transform invoice data from Dynamics 365 Dataverse into a fully validated, mandate-ready e-invoice — ZUGFeRD, XRechnung, Factur-X, or Peppol UBL — and store it in SharePoint or deliver it to your customer automatically.

This post shows you exactly how to build that flow, which format to use for your country, and why adding e-invoice generation as an HTTP action is cleaner than the Word → PDF → email pattern most Microsoft-based businesses still use today.


Why your current Word-to-PDF flow needs to change

Many businesses running Dynamics 365 or Business Central follow a familiar pattern: pull invoice data from Dataverse, generate a Word document from a template, save it to SharePoint, convert it to PDF, email it to the customer.

This worked fine until now. The problem is that since January 2025 in Germany, January 2026 in Belgium, and September 2026 in France, a plain PDF invoice is no longer legally sufficient. Your trading partners and government mandates now require a structured e-invoice — a file that contains machine-readable XML data alongside or instead of the human-readable layout.

Your existing flow produces a PDF. It does not produce a ZUGFeRD file, a Peppol UBL document, or an XRechnung. Those require a different technical process: CII or UBL XML generation, EN 16931 Schematron validation, and — for ZUGFeRD and Factur-X — PDF/A-3b embedding of the XML inside the PDF. None of this is built into Office 365 or Dynamics 365 natively.

The good news: you do not need to rebuild your invoicing flow. You need to replace one step — the Word template generation — with an HTTP action to the InvoiceXML API. Everything else in your flow stays the same.


Which format do you need?

Before building your flow, identify which format applies to your situation. Use this decision table:

Your countryYou invoiceRequired formatEndpoint
🇩🇪 GermanyPrivate companies (B2B)ZUGFeRD/v1/create/zugferd
🇩🇪 GermanyGovernment buyers (B2G)XRechnung/v1/create/xrechnung
🇫🇷 FranceAny business (B2B)Factur-X/v1/create/facturx
🇧🇪 BelgiumAny business (B2B)Peppol UBL/v1/create/ubl
🇳🇱 NetherlandsGovernment buyers (B2G)Peppol UBL/v1/create/ubl
🇳🇴 NorwayAny buyerPeppol UBL (EHF)/v1/create/ubl
🇭🇷 CroatiaAny businessCII or UBL/v1/create/cii
🇵🇹 PortugalAny businessCII (CIUS-PT)/v1/create/cii
🌍 Cross-border EUAny EU businessPeppol UBL/v1/create/ubl

If you operate in multiple countries, use a Switch action in your flow to route to the correct endpoint based on the customer's country field in Dataverse. The API parameters are identical across all endpoints — only the URL changes.


The architecture: before and after

Current flow (Word → PDF):

Dataverse trigger
  → Get invoice record
  → Get line items
  → Populate Word template
  → Convert Word to PDF
  → Save to SharePoint
  → Send email with PDF attachment

New flow (Dataverse → InvoiceXML API):

Dataverse trigger
  → Get invoice record
  → Get line items
  → HTTP POST to InvoiceXML API
  → Save binary response to SharePoint
  → Send email with e-invoice attachment

You remove two steps (Word template population, Word-to-PDF conversion) and replace them with one HTTP action. The output is a validated, mandate-compliant file ready for storage and delivery. No Word template to maintain. No PDF conversion licensing. No compliance logic in your flow.


Setting up the HTTP action

In Power Automate, the InvoiceXML API is called using the built-in HTTP connector (requires a Power Automate Premium licence) or the HTTP with Azure AD connector.

Basic HTTP action configuration

Method:   POST
URI:      https://api.invoicexml.com/v1/create/zugferd
Headers:
  Authorization: Bearer YOUR_API_KEY
  Content-Type:  application/json
Body:     (JSON invoice document, see per-format examples below)

Your API key is available in your InvoiceXML dashboard. Store it as a Power Automate Environment Variable or in Azure Key Vault — do not hardcode it in the flow.


Format 1: ZUGFeRD for German B2B

ZUGFeRD is the correct format for invoices between German private-sector companies. The API returns a PDF/A-3b file with embedded CII XML — your customer receives a PDF they can open in any viewer, while their accounting system (DATEV, SAP, Lexoffice, sevDesk) can automatically import the structured data layer.

HTTP action body

In Power Automate, set the request body to JSON. The API accepts a nested invoice document modelled on EN 16931. Map each value from your Dynamics 365 invoice record:

{
  "invoice": {
    "invoiceNumber": "@{triggerOutputs()?['body/invoicenumber']}",
    "issueDate":     "@{formatDateTime(triggerOutputs()?['body/invoicedate'], 'yyyy-MM-dd')}",
    "dueDate":       "@{formatDateTime(triggerOutputs()?['body/duedate'], 'yyyy-MM-dd')}",
    "currency":      "@{triggerOutputs()?['body/transactioncurrencyid']}",
    "seller": {
      "name":          "@{triggerOutputs()?['body/accountname']}",
      "vatIdentifier": "@{triggerOutputs()?['body/taxid']}",
      "postalAddress": {
        "line1":    "@{triggerOutputs()?['body/address1_line1']}",
        "postCode": "@{triggerOutputs()?['body/address1_postalcode']}",
        "city":     "@{triggerOutputs()?['body/address1_city']}",
        "country":  "DE"
      }
    },
    "buyer": {
      "name":          "@{triggerOutputs()?['body/customeraccountname']}",
      "vatIdentifier": "@{triggerOutputs()?['body/customertaxid']}",
      "postalAddress": {
        "line1":    "@{triggerOutputs()?['body/billingstreet']}",
        "postCode": "@{triggerOutputs()?['body/billingpostalcode']}",
        "city":     "@{triggerOutputs()?['body/billingcity']}",
        "country":  "@{triggerOutputs()?['body/billingcountrycode']}"
      }
    },
    "paymentDetails": {
      "paymentAccountIdentifier": "@{triggerOutputs()?['body/iban']}"
    },
    "lines": [
      {
        "quantity":       10,
        "unitCode":       "HUR",
        "priceDetails":   { "netPrice": 250.00 },
        "vatInformation": { "rate": 19, "categoryCode": "S" },
        "item":           { "name": "Consulting services" }
      }
    ]
  },
  "options": {
    "language": "en",
    "brandColor": "#173a40"
  }
}

You do not send the totals. The API computes each line net amount, the VAT breakdown, and the document totals (tax basis, tax total, grand total) from the line items, then validates the result against EN 16931 before returning the file. typeCode defaults to 380 (commercial invoice), specificationId is set automatically per format, and when you supply an IBAN the payment means defaults to credit transfer.

Line items are a JSON array. To build it from your Dynamics invoice detail records, use a Select action (data operation) that maps each detail row to one line object:

From: @{outputs('Get_invoice_line_items')?['body/value']}
Map:
  quantity                     →  @{item()?['quantity']}
  unitCode                     →  HUR
  priceDetails/netPrice        →  @{item()?['priceperunit']}
  vatInformation/rate          →  19
  vatInformation/categoryCode  →  S
  item/name                    →  @{item()?['productdescription']}

Then reference the Select action's output as the lines array in the request body.

Handling the response

The API returns the ZUGFeRD PDF as a binary file (application/pdf). In Power Automate:

Create file (SharePoint)
  Site:         Your SharePoint site
  Folder:       /Invoices/ZUGFeRD/
  File name:    @{triggerOutputs()?['body/invoicenumber']}.pdf
  File content: @{body('HTTP')}

Then send it by email:

Send an email (Outlook)
  To:              @{triggerOutputs()?['body/emailaddress']}
  Subject:         Invoice @{triggerOutputs()?['body/invoicenumber']}
  Attachments:     @{body('HTTP')}
  Attachment name: @{triggerOutputs()?['body/invoicenumber']}.pdf

The file stored in SharePoint is GoBD-compliant for German archiving purposes — PDF/A-3b format satisfies the immutability and long-term readability requirements of German digital bookkeeping law.


Format 2: XRechnung for German B2G

XRechnung is mandatory for invoices to German federal, state, and municipal buyers. Unlike ZUGFeRD it is pure XML — no PDF wrapper. It is submitted via the ZRE or OZG-RE portal, or transmitted via the Peppol network.

The critical difference from ZUGFeRD: you must supply the Leitweg-ID. This is a routing identifier provided by your government buyer at contract time. It cannot be extracted from any invoice field — it must be stored in your system against the customer record.

Add a custom field to your Dynamics 365 account record for the Leitweg-ID (e.g. cr_leitwegid). Make it mandatory for public sector customers.

HTTP action configuration

URI: https://api.invoicexml.com/v1/create/xrechnung

The body is the same JSON document as the ZUGFeRD call, with one addition: XRechnung carries the Leitweg-ID in the top-level buyerReference field (BT-10). Add it inside the invoice object:

"buyerReference": "@{triggerOutputs()?['body/cr_leitwegid']}"

The Leitweg-ID (buyerReference) is mandatory for XRechnung. If it is missing, the API rejects the request with an HTTP 400 model-validation error (code 4002) before any XML is generated, rather than returning a file. Guard against that with a condition before the HTTP action:

Condition: cr_leitwegid is not empty
  → If yes: proceed to HTTP action
  → If no:  send notification email to invoice team
            "XRechnung generation failed: Leitweg-ID missing for [customer name]"

The API returns XRechnung XML (application/xml). Store it in SharePoint and submit to your Peppol access point or upload manually to the ZRE portal.

Choosing CII or UBL syntax

The /create/xrechnung endpoint returns CII syntax, which is accepted by all German public sector portals. If your submission portal specifically requires UBL syntax, call /create/ubl instead and set the XRechnung CustomizationID on the document:

"specificationId": "urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_3.0"

Both syntaxes are legally equivalent under the XRechnung standard.


Format 3: Factur-X for French B2B

Factur-X is the correct format for French B2B invoicing, mandatory from September 2026. Like ZUGFeRD, it is a hybrid format — a PDF/A-3b with embedded CII XML — so your French customers receive a readable PDF while their accounting system imports the structured data.

URI: https://api.invoicexml.com/v1/create/facturx

The body is identical to ZUGFeRD. Set the seller and buyer postalAddress.country to FR and the line VAT rate to 20 (French standard VAT rate):

"vatInformation": { "rate": 20, "categoryCode": "S" }

French invoices also commonly carry the SIREN/SIRET in the seller VAT identifier, formatted as FR + 2-digit key + 9-digit SIREN:

"seller": {
  "vatIdentifier": "@{triggerOutputs()?['body/fr_taxid']}",
  ...
}

The response is a PDF/A-3b file ready for submission to a certified Plateforme Agréée (PDP) or the public PPF portal from September 2026.


Format 4: Peppol UBL for Belgium, Netherlands, and Peppol markets

Peppol UBL is mandatory for Belgian B2B invoicing since January 2026, and the standard for the Netherlands, Norway, Denmark, and cross-border Peppol transactions globally. Unlike ZUGFeRD and Factur-X, the output is pure UBL XML — it must be transmitted via a Peppol access point, not emailed directly.

URI: https://api.invoicexml.com/v1/create/ubl

The endpoint produces Peppol BIS Billing 3.0 by default. To target a different national CIUS, set specificationId (the CustomizationID) on the invoice document:

// Peppol BIS 3.0 - Belgium, Denmark, Sweden, cross-border (default, may be omitted)
"specificationId": "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0"

// NLCIUS - Netherlands public sector
"specificationId": "urn:cen.eu:en16931:2017#compliant#urn:fdc:nen.nl:nlcius:v1.0"

// EHF 3.0 - Norway
"specificationId": "urn:cen.eu:en16931:2017#compliant#urn:fdc:anskaffelser.no:2019:ehf:spec:3.0"

For Belgian invoices, you also need the buyer's enterprise number as a Peppol endpoint identifier. Add a custom field to your Dynamics account record for the Belgian enterprise number (BE + 10 digits) and set it on the buyer's electronicAddress:

"buyer": {
  ...
  "electronicAddress": {
    "identifier": "@{triggerOutputs()?['body/cr_enterprisenumber']}",
    "schemeId":   "0208"
  }
}

The 0208 scheme code identifies Belgian Crossroads Bank for Enterprises (KBO/BCE) numbers in the Peppol network.

After generating the UBL XML, your flow should pass it to your Peppol access point provider's API for network delivery, rather than emailing it directly. The SharePoint save step remains the same for archiving.


Validating before storing and sending

For high-stakes or high-volume flows, add a validation step before storing and sending. This catches any field mapping errors — missing VAT numbers, arithmetic inconsistencies, malformed IBANs — before a non-compliant invoice reaches your customer.

Add an HTTP action immediately after the create call:

Method: POST
URI:    https://api.invoicexml.com/v1/validate/zugferd
        (or /validate/xrechnung, /validate/ubl)
Body:   file: @{body('Create_Invoice_HTTP')}

Then add a condition:

Condition: @{body('Validate_HTTP')?['valid']} is equal to true
  → If yes: save to SharePoint, send to customer
  → If no:  send internal alert with error details
            "@{body('Validate_HTTP')?['errors']}"

The errors array is a flat list of finding objects, each with a rule code (e.g. BR-01), a plain-language message, the affected btCodes and fields, and a verbatim raw string. A parallel warnings array uses the same shape. A finding looks like this:

{
  "rule":     "BR-01",
  "line":     null,
  "message":  "An Invoice shall have a Specification identifier.",
  "btCodes":  ["BT-24"],
  "fields":   ["specificationId"],
  "raw":      "[BR-01] EN16931: An Invoice shall have a Specification identifier (at /*:CrossIndustryInvoice...)"
}

The message field carries a plain-language description you can include directly in a Teams notification or email to your finance team, with no technical knowledge required to understand what needs fixing.


Complete flow structure

Here is the complete Power Automate flow for German ZUGFeRD invoicing:

 1. Trigger: When a Dataverse record is updated
    (invoice status changes to "Ready to Send")

 2. Get invoice record
    (fetch all fields from the invoice entity)

 3. Get invoice line items
    (list rows from invoice detail entity, filtered by invoice ID)

 4. Build lines array
    (Apply to each → compose line item parameters)

 5. HTTP: Create ZUGFeRD
    POST https://api.invoicexml.com/v1/create/zugferd
    [invoice parameters + line items]

 6. Condition: HTTP status code is 200
    → If no: Send Teams notification "Invoice generation failed"
             Include: @{body('HTTP')?['detail']}
             Terminate flow

 7. HTTP: Validate ZUGFeRD
    POST https://api.invoicexml.com/v1/validate/zugferd
    file: @{body('Create_ZUGFeRD_HTTP')}

 8. Condition: valid is true
    → If no: Send Teams notification "Invoice validation failed"
             Include: @{body('Validate_HTTP')?['errors']}
             Terminate flow

 9. Create file (SharePoint)
    /Invoices/ZUGFeRD/@{invoicenumber}.pdf
    Content: @{body('Create_ZUGFeRD_HTTP')}

10. Update Dataverse record
    invoice status → "Sent"
    invoice file path → SharePoint URL

11. Send email (Outlook)
    To: customer email
    Attachment: @{body('Create_ZUGFeRD_HTTP')}

Dynamics 365 field mapping reference

Standard Dynamics 365 invoice entity fields and their InvoiceXML JSON paths (all live inside the invoice object):

Dynamics 365 fieldDataverse logical nameInvoiceXML JSON path
Invoice NumberinvoicenumberinvoiceNumber
Invoice DatecreatedonissueDate
Due DateduedatedueDate
Account Namecustomerid_account.namebuyer.name
Billing Streetbillto_line1buyer.postalAddress.line1
Billing Postal Codebillto_postalcodebuyer.postalAddress.postCode
Billing Citybillto_citybuyer.postalAddress.city
Total Taxtotaltaxtotals.taxTotalAmount (optional, auto-calculated)
Total Amounttotalamounttotals.taxBasisTotalAmount (optional, auto-calculated)
Total incl. Taxtotalamountlessfreighttotals.grandTotalAmount (optional, auto-calculated)
Currencytransactioncurrencyidcurrency
Product Descriptioninvoicedetail.productdescriptionlines[n].item.name
Quantityinvoicedetail.quantitylines[n].quantity
Price Per Unitinvoicedetail.priceperunitlines[n].priceDetails.netPrice
Extended Amountinvoicedetail.extendedamountlines[n].lineNetAmount (optional, auto-calculated)

Your seller details (name, VAT number, address, IBAN) are typically static, so store them as Power Automate Environment Variables rather than fetching from Dataverse on every run.


Why this beats building it into your Dynamics customisation

The alternative to using InvoiceXML is implementing ZUGFeRD or UBL generation inside Dynamics 365 itself — either as a custom plugin, a PCF control, or a .NET Azure Function that your cloud flow calls. This is technically achievable. Here is what it actually involves:

For ZUGFeRD: CII XML generation, Saxon-HE Schematron validation (requires IKVM cross-compilation for .NET), PDF/A-3b container creation with correct ICC colour profiles, XMP metadata, and AFRelationship declarations. The PDF/A-3b layer alone has four distinct failure modes that are invisible in a standard PDF viewer.

For Peppol UBL: UBL 2.1 XML generation, EN 16931 Schematron, Peppol BIS 3.0 Schematron overlay (updates quarterly from OpenPeppol), CustomizationID profile routing across multiple national CIUS variants.

The maintenance commitment: Every time FeRD publishes a new ZUGFeRD version, every time KoSIT releases a new XRechnung CIUS, every time OpenPeppol updates Peppol BIS, your custom implementation needs to be updated, tested, and redeployed. These updates happen multiple times per year across the formats combined.

InvoiceXML maintains all of this as a service. When ZUGFeRD 2.4 shipped in 2025 with mandatory service period dates and deprecated NIL elements, our validation rules updated before the version's effective date. Your flow did not change. Your customers did not see a rejection.

Additionally: invoices contain sensitive financial data — VAT numbers, IBANs, payment amounts, business relationships. Every invoice that passes through a self-hosted processing function is a GDPR consideration. InvoiceXML is stateless by design — every document is processed in memory and purged immediately on response delivery. Nothing is written to disk, nothing is retained. GDPR and HIPAA compliant by architecture, not configuration.


Microsoft Power Apps and other Power Platform tools

The same HTTP action pattern works across the Power Platform:

Power Apps: Call the InvoiceXML API from a Power Apps custom connector. Add a button to your Dynamics-connected app that generates and downloads a ZUGFeRD invoice on demand, without leaving the application.

Logic Apps: The Azure Logic Apps equivalent of Power Automate. The HTTP action configuration is identical — useful when your invoicing flow runs as part of a broader Azure-hosted integration.

Azure Functions: For high-volume or programmatic scenarios, call the InvoiceXML API from an Azure Function triggered by a Dataverse change event. The Function handles batching, error retry logic, and Peppol access point submission in a single serverless workflow.

SharePoint + Power Automate: Trigger invoice generation from a SharePoint list item status change. Store the resulting e-invoice back to the same library with a structured folder hierarchy — /Invoices/{Year}/{Month}/{InvoiceNumber}.pdf.


Get started

Create your free InvoiceXML account → — includes 100 free credits. No credit card required for the trial.

If you are building a Power Automate flow and want help mapping your specific Dynamics 365 fields to the API parameters, contact us directly — we are happy to review your setup and suggest the correct field mapping for your invoice entity structure.

Related resources:


InvoiceXML is the e-invoice compliance layer for Microsoft Power Platform, Dynamics 365, and any cloud-based invoicing workflow. It handles ZUGFeRD, Factur-X, XRechnung, UBL, and CII via a single REST API — with stateless processing, automatic standard updates, and no compliance infrastructure to maintain inside your Dynamics customisation.

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