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 country | You invoice | Required format | Endpoint |
|---|---|---|---|
| 🇩🇪 Germany | Private companies (B2B) | ZUGFeRD | /v1/create/zugferd |
| 🇩🇪 Germany | Government buyers (B2G) | XRechnung | /v1/create/xrechnung |
| 🇫🇷 France | Any business (B2B) | Factur-X | /v1/create/facturx |
| 🇧🇪 Belgium | Any business (B2B) | Peppol UBL | /v1/create/ubl |
| 🇳🇱 Netherlands | Government buyers (B2G) | Peppol UBL | /v1/create/ubl |
| 🇳🇴 Norway | Any buyer | Peppol UBL (EHF) | /v1/create/ubl |
| 🇭🇷 Croatia | Any business | CII or UBL | /v1/create/cii |
| 🇵🇹 Portugal | Any business | CII (CIUS-PT) | /v1/create/cii |
| 🌍 Cross-border EU | Any EU business | Peppol 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, with 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 UBL syntax by default, which is accepted by all German public sector portals. If your submission portal specifically requires the CII syntax, add the syntax option to the request body:
"options": { "syntax": "cii" }
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 inbound invoices (outbound is already validated)
Notice what the flow does not contain: a validation step after the create call. It does not need one. Every document produced by /v1/create/* is validated inside the API before the response is returned: XSD schema, EN 16931 Schematron, the applicable national CIUS overlay (KoSIT for XRechnung, Peppol BIS for UBL, FNFE for Factur-X), and, for the hybrid formats, PDF/A-3b conformance of the container itself.
If a field mapping in your flow is wrong (a missing VAT number, an arithmetic inconsistency, a malformed IBAN), the API returns an HTTP 400 naming the failing rules instead of a file. A 200 response is a compliant invoice by definition. That is the point of the create endpoints: a non-compliant invoice never leaves the API, so there is nothing left for your flow to check before saving to SharePoint or emailing the customer. The status-code condition in step 6 of the flow below is the whole safety net.
Where validation belongs in Power Automate is the inbound side: e-invoices your suppliers send you, and files produced by systems other than InvoiceXML. Those carry no guarantee at all, and the German, Belgian, and French mandates all oblige you to receive and process structured invoices, not just issue them.
Inbound: supplier invoices arriving in a shared mailbox
The most common inbound pattern is an accounts payable mailbox. Validate each attachment before it becomes a supplier invoice record in Dynamics:
1. Trigger: When a new email arrives (shared mailbox)
[email protected], include attachments: Yes
2. Apply to each: @{triggerOutputs()?['body/attachments']}
3. HTTP: Validate the attachment
POST https://api.invoicexml.com/v1/validate/zugferd
Body: file: @{items('Apply_to_each')?['contentBytes']}
4. Condition: @{body('Validate_HTTP')?['valid']} is equal to true
→ If yes: create the supplier invoice record in Dataverse,
archive the file to SharePoint
→ If no: route to the AP review queue and notify the supplier
"@{body('Validate_HTTP')?['errors']}"
Pick the validate endpoint by file type with a Switch action on the attachment name: .pdf attachments go to /v1/validate/zugferd (German suppliers) or /v1/validate/facturx (French suppliers), .xml attachments go to /v1/validate/xrechnung, /v1/validate/ubl, or /v1/validate/cii. Within each endpoint, the conformance profile is detected from the document itself and reported back in data.profile; you do not need to know in advance whether a supplier sent ZUGFeRD BASIC, EN 16931, or EXTENDED.
Inbound: files from systems you do not control
The same call is the gate for any XML that reaches your flow from somewhere other than InvoiceXML: a legacy billing system, an acquired subsidiary still on its own ERP, a fulfilment partner that invoices on your behalf. Validate before you forward it to your Peppol access point or post it to Chorus Pro, so a rejection surfaces in your own Teams channel rather than at the network level, where it arrives days later as an unhelpful transport error.
Method: POST
URI: https://api.invoicexml.com/v1/validate/ubl
(or /validate/cii, /validate/xrechnung)
Body: file: @{body('Get_file_content')}
Condition: @{body('Validate_HTTP')?['valid']} is equal to true
→ If yes: submit to the Peppol access point
→ If no: hold the document, send internal alert
"@{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 put straight into a Teams notification, an AP queue comment, or a reply to the supplier, 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
(the file is validated and compliant; anything else is a
400 listing the rules that failed)
→ If no: Send Teams notification "Invoice generation failed"
Include: @{body('HTTP')?['detail']}
Terminate flow
7. Create file (SharePoint)
/Invoices/ZUGFeRD/@{invoicenumber}.pdf
Content: @{body('Create_ZUGFeRD_HTTP')}
8. Update Dataverse record
invoice status → "Sent"
invoice file path → SharePoint URL
9. Send email (Outlook)
To: customer email
Attachment: @{body('Create_ZUGFeRD_HTTP')}
Nine steps, one API call, no validation branch. The compliance check that would otherwise be steps 7 and 8 happened inside the create call before the file was handed back to your flow.
Dynamics 365 field mapping reference
Standard Dynamics 365 invoice entity fields and their InvoiceXML JSON paths (all live inside the invoice object):
| Dynamics 365 field | Dataverse logical name | InvoiceXML JSON path |
|---|---|---|
| Invoice Number | invoicenumber | invoiceNumber |
| Invoice Date | createdon | issueDate |
| Due Date | duedate | dueDate |
| Account Name | customerid_account.name | buyer.name |
| Billing Street | billto_line1 | buyer.postalAddress.line1 |
| Billing Postal Code | billto_postalcode | buyer.postalAddress.postCode |
| Billing City | billto_city | buyer.postalAddress.city |
| Total Tax | totaltax | totals.taxTotalAmount (optional, auto-calculated) |
| Total Amount | totalamount | totals.taxBasisTotalAmount (optional, auto-calculated) |
| Total incl. Tax | totalamountlessfreight | totals.grandTotalAmount (optional, auto-calculated) |
| Currency | transactioncurrencyid | currency |
| Product Description | invoicedetail.productdescription | lines[n].item.name |
| Quantity | invoicedetail.quantity | lines[n].quantity |
| Price Per Unit | invoicedetail.priceperunit | lines[n].priceDetails.netPrice |
| Extended Amount | invoicedetail.extendedamount | lines[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 → and get 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:
- E-invoicing in Dynamics 365 Business Central: ZUGFeRD and Factur-X
- E-invoicing in Salesforce: Peppol UBL, ZUGFeRD, Factur-X and XRechnung from Flow and Apex
- E-invoicing on SAP BTP: ZUGFeRD, XRechnung and Factur-X from Integration Suite
- E-invoicing from Google Sheets: ZUGFeRD, Factur-X and XRechnung with Apps Script
- E-invoicing in Zapier: three InvoiceXML apps for ZUGFeRD, XRechnung, Factur-X, CII and UBL
- ZUGFeRD API: the complete toolkit
- XRechnung API: the complete toolkit
- Factur-X API: the complete toolkit
- UBL API: the complete toolkit
- EN 16931 API: the complete compliance toolkit
- E-invoicing mandates by country
- Full API documentation
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.