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: multipart/form-data
Body: (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 body as multipart/form-data. Map each field from your Dynamics 365 invoice record:
{
"InvoiceNumber": "@{triggerOutputs()?['body/invoicenumber']}",
"IssueDate": "@{formatDateTime(triggerOutputs()?['body/invoicedate'], 'yyyy-MM-dd')}",
"PaymentDueDate": "@{formatDateTime(triggerOutputs()?['body/duedate'], 'yyyy-MM-dd')}",
"SellerName": "@{triggerOutputs()?['body/accountname']}",
"SellerTaxId": "@{triggerOutputs()?['body/taxid']}",
"SellerStreet": "@{triggerOutputs()?['body/address1_line1']}",
"SellerPostcode": "@{triggerOutputs()?['body/address1_postalcode']}",
"SellerCity": "@{triggerOutputs()?['body/address1_city']}",
"SellerCountry": "DE",
"BuyerName": "@{triggerOutputs()?['body/customeraccountname']}",
"BuyerTaxId": "@{triggerOutputs()?['body/customertaxid']}",
"BuyerStreet": "@{triggerOutputs()?['body/billingstreet']}",
"BuyerPostcode": "@{triggerOutputs()?['body/billingpostalcode']}",
"BuyerCity": "@{triggerOutputs()?['body/billingcity']}",
"BuyerCountry": "@{triggerOutputs()?['body/billingcountrycode']}",
"Currency": "@{triggerOutputs()?['body/transactioncurrencyid']}",
"TaxBasisTotal": "@{triggerOutputs()?['body/totalamount_base']}",
"TaxTotalAmount": "@{triggerOutputs()?['body/totaltax']}",
"GrandTotalAmount": "@{triggerOutputs()?['body/totalamountinclusivoftax']}",
"PaymentMeansCode": "58",
"IBAN": "@{triggerOutputs()?['body/iban']}",
"profile": "en16931"
}
Line items require an Apply to each loop over your invoice detail records before the HTTP action, building a lines array. Alternatively, pass the line items as indexed parameters:
Lines[0][description] → @{items('Apply_to_each')?['productdescription']}
Lines[0][quantity] → @{items('Apply_to_each')?['quantity']}
Lines[0][unitPrice] → @{items('Apply_to_each')?['priceperunit']}
Lines[0][lineTotal] → @{items('Apply_to_each')?['extendedamount']}
Lines[0][unitCode] → HUR
Lines[0][taxPercentage] → 19
Lines[0][taxCategoryCode]→ S
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
Add one additional parameter compared to the ZUGFeRD call:
"BuyerReference": "@{triggerOutputs()?['body/cr_leitwegid']}"
If this field is empty, the API returns a 4002 error immediately. Add 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
XRechnung supports both. The default is CII. If your submission portal requires UBL syntax, add:
"syntax": "ubl"
Both are legally equivalent and accepted by all German public sector portals.
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 parameters are identical to ZUGFeRD. Change SellerCountry and BuyerCountry to FR and update the tax rate to 20% (French standard VAT rate):
"Lines[0][taxPercentage]": "20"
French invoices also commonly include the SIREN/SIRET number in the seller tax ID field — format it as FR + 2-digit key + 9-digit SIREN:
"SellerTaxId": "@{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
Add the profile parameter to set the correct CIUS for the destination country:
"profile": "peppol-bis-3" ← Belgium, Denmark, Sweden, cross-border
"profile": "nlcius" ← Netherlands public sector
"profile": "ehf" ← Norway
For Belgian invoices, you also need the buyer's enterprise number as an endpoint identifier. Add a custom field to your Dynamics account record for the Belgian enterprise number (BE + 10 digits):
"BuyerEndpointId": "@{triggerOutputs()?['body/cr_enterprisenumber']}",
"BuyerEndpointScheme": "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']?['friendly']}"
The errors.friendly array contains plain-language error descriptions you can include directly in a Teams notification or email to your finance team — 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']?['friendly']}
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 parameter equivalents:
| Dynamics 365 field | Dataverse logical name | InvoiceXML parameter |
|---|---|---|
| Invoice Number | invoicenumber | InvoiceNumber |
| Invoice Date | createdon | IssueDate |
| Due Date | duedate | PaymentDueDate |
| Account Name | customerid_account.name | BuyerName |
| Billing Street | billto_line1 | BuyerStreet |
| Billing Postal Code | billto_postalcode | BuyerPostcode |
| Billing City | billto_city | BuyerCity |
| Total Tax | totaltax | TaxTotalAmount |
| Total Amount | totalamount | TaxBasisTotal |
| Total incl. Tax | totalamountlessfreight | GrandTotalAmount |
| Currency | transactioncurrencyid | Currency |
| Product Description | invoicedetail.productdescription | Lines[n][description] |
| Quantity | invoicedetail.quantity | Lines[n][quantity] |
| Price Per Unit | invoicedetail.priceperunit | Lines[n][unitPrice] |
| Extended Amount | invoicedetail.extendedamount | Lines[n][lineTotal] |
Your seller details (name, VAT number, address, IBAN) are typically static — 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:
- 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.