Validate e-invoices in the SDK: XRechnung, ZUGFeRD and Factur-X (beta)

Code samples
VB.NET, C#, VB6 / VBScript (COM)
Published

In short: From version 11.0.0.10 the Aloaha ZUGFeRD SDK validates an e-invoice itself – with ValidateInvoiceXML, ValidateInvoicePDF or ValidateInvoiceXMLString. It checks against the XML schema and the published rules: for XRechnung 3.0 and EN 16931 the same rules as the KoSIT validator, for the ZUGFeRD / Factur-X profiles MINIMUM, BASIC WL, BASIC and EXTENDED the official rules of FeRD and FNFE (ZUGFeRD 2.5.2 / Factur-X 1.09.2). So every kind of invoice the SDK creates can be checked. The check runs offline inside your program, without Java and without uploading the invoice anywhere. Every message comes as a plain-language explanation with the affected fields (BT numbers) and as the official rule text. The feature is beta until further notice, and we welcome any feedback.

At a glance

QuestionAnswer
What is checked?CII invoices: XRechnung 3.0 (incl. Extension and CVD) and ZUGFeRD / Factur-X in all profiles – MINIMUM, BASIC WL, BASIC, EN 16931 and EXTENDED. Depending on the profile: XML schema, EN 16931 rules (BR-…, BR-CO-…, BR-CL-…, CII-SR-…), XRechnung rules (BR-DE-…) and the Factur-X rules of the profile (e.g. BR-FXEXT-…)
Which input?An XML file, XML as text (e.g. from a database) or the XML embedded in a ZUGFeRD/XRechnung PDF
ResultTrue = accepted (no errors), False = rejected; plus messages with rule ID, level, location and explanation
LanguagesExplanations and report in German or English (ValidationLanguage)
Not checkedUBL invoices, ZUGFeRD 1.0, XRechnung versions before 3.0 (the message names the identifier found) and the PDF/A conformance of the PDF itself
RequirementAloaha ZUGFeRD SDK 11.0.0.10 or later with a valid licence; with a valid maintenance contract the upgrade is free
StatusBeta until further notice – feedback to info@aloaha.com

Defendant and judge in one person?

An honest remark first: whoever creates invoices should not be the only one to declare them valid. The SDK creates your e-invoice – if the same SDK then checks it, we would be defendant and judge in one person. So the rule is: whether an invoice is accepted is always decided by the receiver's system. For new kinds of invoices, new customers or after changes to your software, also check sample invoices independently, for example with the official KoSIT validator – see How do I validate my XRechnung before sending it?

That is why we built the check so that it does not judge by rules of its own: it uses only the published rules of CEN and KoSIT, and every message carries the official rule ID and rule text. In our tests it reported the same rules, levels, locations and results as KoSIT validator 1.6.3 with the validation configuration of 31 August 2026 for all 5,944 test invoices. For the ZUGFeRD profiles MINIMUM to EXTENDED, all messages, locations and results matched the official rules for 10,179 test documents.

The built-in check is especially useful in these cases:

  • Incoming invoices: you check invoices that others send you. There is no conflict of interest – the SDK did not create them.
  • Invoices from other programs: your ERP or billing software creates the XML itself, and the SDK only embeds it into the PDF or reads it.
  • Pre-check before sending: every invoice is checked before it leaves the company. Errors such as wrong totals or a cash discount only in free text show up immediately, not at the receiver.
  • Data protection: invoices contain business data. The check runs locally, nothing is uploaded to a validation portal.
  • Bulk checks and automation: whole folders or databases can be checked without a web interface, for example after an update of your software.
  • Development and support: the plain-language messages name the affected field and the matching SDK function. That makes troubleshooting much faster.

Beta: your feedback is welcome

The check is a beta feature until further notice. In our tests the results match the KoSIT validator, but there may be invoices we have not seen yet. If you notice a difference to another validation tool or an explanation is unclear, please send us the invoice (anonymised if you like) and the report from ValidationReport to info@aloaha.com.

Example: check an XML file (VB.NET)

The most common case: the invoice was saved with SaveXML and is to be checked before sending. The return value tells you whether the invoice is accepted. LastError is only set for technical problems, for example a missing file or a licence problem – the validation result itself is only in the Validation… members.

' VB.NET
Dim alo As New Aloaha_ZUGFeRD.XML("YOUR-LICENCE-KEY")
alo.ValidationLanguage = "en"
Dim file As String = "C:\Invoices\INV-1001.xml"

If alo.ValidateInvoiceXML(file) Then
    Console.WriteLine("Accepted, scenario: " & alo.ValidationScenario)
ElseIf alo.LastError <> "" Then
    Console.WriteLine("Check not possible: " & alo.LastError)
Else
    Console.WriteLine("Rejected, errors: " & alo.ValidationErrorCount)
End If

For i As Integer = 0 To alo.ValidationMessageCount - 1
    Console.WriteLine(alo.ValidationMessageLevel(i) & " " & alo.ValidationMessageID(i))
    Console.WriteLine("  " & alo.ValidationMessageText(i))
    Console.WriteLine("  Location: " & alo.ValidationMessageLocation(i))
Next

An accepted invoice can still have warnings and information messages; they are in the message list too. The message index starts at 0.

Example: check right after creating and save the report

' VB.NET – at the end of your invoice creation
alo.SaveXML("C:\Invoices\INV-1001.xml", "YOUR-LICENCE-KEY")

If Not alo.ValidateInvoiceXML("C:\Invoices\INV-1001.xml") Then
    System.IO.File.WriteAllText("C:\Invoices\INV-1001_report.txt", alo.ValidationReport, System.Text.Encoding.UTF8)
    ' do not send the invoice, pass the report to accounting or support
End If

Example: check a ZUGFeRD or XRechnung PDF

ValidateInvoicePDF extracts the embedded invoice XML from the PDF and checks it. If the PDF contains no invoice XML, the function returns False and LastError gives the reason.

' VB.NET
If Not alo.ValidateInvoicePDF("C:\Invoices\INV-1001.pdf") Then
    If alo.LastError <> "" Then
        MsgBox("No invoice to check: " & alo.LastError)
    Else
        MsgBox(alo.ValidationReport)
    End If
End If

The XML inside the PDF is checked, not the PDF/A conformance of the file itself.

Example: C#

// C#
var alo = new Aloaha_ZUGFeRD.XML("YOUR-LICENCE-KEY");
alo.ValidationLanguage = "en";

bool accepted = alo.ValidateInvoicePDF(@"C:\Invoices\INV-1001.pdf");
if (!accepted && alo.LastError != "")
{
    Console.WriteLine("Check not possible: " + alo.LastError);
}
for (int i = 0; i < alo.ValidationMessageCount; i++)
{
    Console.WriteLine(alo.ValidationMessageLevel(i) + " " + alo.ValidationMessageID(i) + ": " + alo.ValidationMessageText(i));
}

Example: VBA, Access and VB6 (COM)

Through COM you pass the licence key as the second parameter. Print the full report to the Immediate window, because MsgBox cuts long texts.

' VBA / VB6 (COM)
Dim alo As Object, i As Long
Set alo = CreateObject("Aloaha_ZUGFeRD.XML")
alo.ValidationLanguage = "en"

If alo.ValidateInvoiceXML("C:\Invoices\INV-1001.xml", "YOUR-LICENCE-KEY") Then
    MsgBox "Invoice accepted (" & alo.ValidationScenario & ")"
ElseIf alo.LastError <> "" Then
    MsgBox "Check not possible: " & alo.LastError
Else
    Debug.Print alo.ValidationReport
    For i = 0 To alo.ValidationMessageCount - 1
        If alo.ValidationMessageLevel(i) = "error" Then
            Debug.Print alo.ValidationMessageID(i) & ": " & alo.ValidationMessageText(i)
        End If
    Next
End If

If the invoice XML is stored in a database field, check the text directly with ValidateInvoiceXMLString:

' VBA (Access)
Dim xmlText As String
xmlText = Nz(DLookup("XmlContent", "tblInvoices", "InvoiceNo='INV-1001'"), "")
If Not alo.ValidateInvoiceXMLString(xmlText, "YOUR-LICENCE-KEY") Then Debug.Print alo.ValidationReport

Example: incoming invoices – check a folder

' VB.NET – check all incoming PDF invoices and log the result as CSV
Dim alo As New Aloaha_ZUGFeRD.XML("YOUR-LICENCE-KEY")
Dim log As String = "C:\Inbox\check.csv"
System.IO.File.WriteAllText(log, "File;Result;Errors;Warnings;Note" & vbCrLf)

For Each file As String In System.IO.Directory.GetFiles("C:\Inbox", "*.pdf")
    Dim ok As Boolean = alo.ValidateInvoicePDF(file)
    Dim result As String = If(ok, "accepted", If(alo.LastError <> "", "not checkable", "rejected"))
    System.IO.File.AppendAllText(log, System.IO.Path.GetFileName(file) & ";" & result & ";" &
        alo.ValidationErrorCount & ";" & alo.ValidationWarningCount & ";" & alo.LastError & vbCrLf)
Next

Reading the result

MemberContent
Return valueTrue = accepted; False = rejected or not checkable
ValidationScenarioxrechnung, xrechnung-extension, xrechnung-cvd, en16931, minimum, basicwl, basic, extended or none (not checked)
ValidationErrorCount, ValidationWarningCountNumber of errors and warnings
ValidationMessageCountNumber of all messages (errors, warnings, information)
ValidationMessageID(i)Rule ID, e.g. BR-CO-10, BR-DE-18, BR-FXEXT-03; XSD for schema errors; for the structure rules of the ZUGFeRD profiles FX-NOT-USED, FX-OCCURS, FX-CODE
ValidationMessageLevel(i)error, warning or information
ValidationMessageText(i)Plain-language explanation with affected fields, the value found and the matching SDK function
ValidationMessageRuleText(i)Official rule text (EN 16931 and Factur-X rules in English, XRechnung rules in German)
ValidationMessageLocation(i)Location in the XML, e.g. /rsm:CrossIndustryInvoice[1]/rsm:SupplyChainTradeTransaction[1]
ValidationReportFull report as text
ValidationRulesVersionRule versions used
ValidationLanguagede (default) or en

The levels follow the assessment of the KoSIT validation configuration: for XRechnung, for example, BR-CL-21 only counts as a warning, while CII-SR-465 counts as an error. Warnings and information messages do not prevent acceptance. In the ZUGFeRD profiles every violated rule is an error; elements the profile does not use (FX-NOT-USED) are warnings – the receiver ignores them.

Plain-language messages instead of rule jargon

The official rule texts are precise but hard to read. That is why ValidationMessageText gives a plain-language explanation for each rule: what is wrong, which fields are affected (BT number and name), which value was found and which SDK function sets the field. This is the report for an invoice with a wrong cash discount line, with ValidationLanguage = "en":

Result: rejected
Scenario: XRechnung 3.0 (CIUS)
XML schema: valid
Errors: 1, warnings: 0, information: 0

[ERROR] BR-DE-18
  XRechnung: the cash discount line in the payment terms (BT-20) does not have the required
  format, e.g. #SKONTO#TAGE=14#PROZENT=2.00# followed by a line break. Fields: BT-20 Payment
  terms; BT-115 Amount due for payment. SDK: AddTradePaymentTerms, TradePaymentTerm.
  Location: /rsm:CrossIndustryInvoice[1]/rsm:SupplyChainTradeTransaction[1]/…
  Rule text: [BR-DE-18] Die Informationen zur Gewährung von Skonto müssen wie folgt …

With the default ValidationLanguage = "de" explanations and report are in German; the official rule texts stay as published.

Which invoices are checked?

As with the KoSIT validator, the specification identifier (BT-24) decides which rules apply. For the ZUGFeRD profiles the equivalent identifiers with urn:zugferd.de:2p0:… count too.

BT-24 in the invoiceCheck
urn:cen.eu:en16931:2017 (ZUGFeRD/Factur-X profile EN 16931)Schema + EN 16931 rules
urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0Schema + EN 16931 rules + XRechnung rules
XRechnung 3.0 Extension or CVDAs XRechnung, with the assessment of these variants
urn:factur-x.eu:1p0:minimum (MINIMUM)Schema + rules of the MINIMUM profile
urn:factur-x.eu:1p0:basicwl (BASIC WL)Schema + rules of the BASIC WL profile
urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic (BASIC)Schema + rules of the BASIC profile
urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended (EXTENDED)Schema + rules of the EXTENDED profile
others, e.g. UBL, ZUGFeRD 1.0 or XRechnung 2.xNo check, message NO-SCENARIO with the identifier found, result False

How the check works

The rules for EN 16931 and XRechnung are published as Schematron, a rule language normally executed by a Java program such as the KoSIT validator. We translated the published rules into VB.NET code that runs directly inside the SDK – without Java, without an extra installation and with the same arithmetic (e.g. for rounding and decimals). The CII schema is included in the SDK. FeRD and FNFE publish the rules for the ZUGFeRD profiles as compiled XSLT; we turned them back into Schematron rules and translated them into VB.NET code as well, including the code lists of the profiles. We take over new rule versions from KoSIT and FeRD with an SDK update; before each release we compare the results with the official tools again.

We tested with 5,944 invoices: the official test files of KoSIT and CEN, 3,363 deliberately broken invoices and 2,400 invoices from our own regression tests. Rule IDs, levels, locations, rule texts and the result matched the KoSIT validator in all cases. For the ZUGFeRD profiles MINIMUM, BASIC WL, BASIC and EXTENDED we checked 10,179 documents – 1,826 real invoices (including SDK output and the official examples), 1,169 profile-conformant invoices and 7,184 deliberately broken variants – and compared them with the official XSLT rules run in Saxon: messages, locations and results were identical.

Where the rules come from

The check uses these published rule sets. We thank the publishers for their work:

  • EN 16931 validation artefacts (CII) 1.3.16 by CEN/TC 434 – github.com/ConnectingEurope/eInvoicing-EN16931, licence EUPL 1.2
  • XRechnung Schematron 2.6.0 for XRechnung 3.0.2 by the Koordinierungsstelle für IT-Standards (KoSIT) – github.com/itplr-kosit/xrechnung-schematron, licence Apache 2.0
  • Assessment of the messages according to the KoSIT validation configuration for XRechnung of 31 August 2026 – github.com/itplr-kosit/validator-configuration-xrechnung
  • XML schema UN/CEFACT Cross Industry Invoice D16B (uncoupled), as used in the KoSIT validation configuration
  • ZUGFeRD 2.5.2 / Factur-X 1.09.2 – XML schemas, validation rules (Schematron) and code lists of the profiles MINIMUM, BASIC WL, BASIC and EXTENDED by FeRD (Forum elektronische Rechnung Deutschland) and FNFE-MPE – terms of use: Apache 2.0
  • Reference in our tests: KoSIT validator 1.6.3 and, for the ZUGFeRD profiles, the official XSLT rules run with Saxon-HE 12.5

Typical questions when checking

  • False, but LastError is empty: the invoice was checked and rejected. The reasons are in the messages or in ValidationReport.
  • False and LastError = “License Problem”: the licence key is missing or invalid. Pass it as a parameter or in the constructor.
  • Message NO-SCENARIO: the file is not checked, for example a UBL invoice, ZUGFeRD 1.0 or an XRechnung before version 3.0. The message names the specification identifier (BT-24) found.
  • Message FX-NOT-USED (warning): an element is not used in the chosen ZUGFeRD profile and is ignored by the receiver. Leave it out or use a richer profile.
  • Message FX-CODE or FX-OCCURS: a code is not in the code list of the profile, or an element occurs too often or not at all.
  • Message XSD: the structure does not match the CII schema, for example an element in the wrong place. Then – like the KoSIT validator – the SDK does not check the business rules.
  • Message PROCESSING-ERROR: a field occurs several times or with unexpected content, so a rule set could not be checked completely. The KoSIT validator rejects such invoices too.
  • Cash discount error BR-DE-18: the format is explained in Skonto in der XRechnung (German).

Frequently asked questions

Can the Aloaha ZUGFeRD SDK validate an XRechnung?

Yes, from version 11.0.0.10 with ValidateInvoiceXML, ValidateInvoicePDF and ValidateInvoiceXMLString. It checks against the XML schema and the published rules for XRechnung 3.0, EN 16931 and the ZUGFeRD profiles MINIMUM to EXTENDED. The feature is beta.

Does the built-in check replace the KoSIT validator?

No. It uses the same rules and gave the same results in our tests, but the SDK also created the invoice. For new kinds of invoices, check samples independently as well; the receiver's system decides.

Are invoices sent over the internet for the check?

No. The check runs completely locally in the SDK, without Java and without upload.

Can I also check the XML inside a ZUGFeRD PDF?

Yes, with ValidateInvoicePDF. The SDK extracts the embedded invoice XML and checks it. The PDF/A conformance of the file itself is not checked.

Are EXTENDED, BASIC and MINIMUM invoices checked too?

Yes. For MINIMUM, BASIC WL, BASIC and EXTENDED the SDK checks against the XML schema and the official rules of the profile according to ZUGFeRD 2.5.2 / Factur-X 1.09.2. UBL invoices, ZUGFeRD 1.0 and XRechnung versions before 3.0 are not checked.

Are the messages also available in English?

Yes. With ValidationLanguage = "en" explanations and report are in English. The official rule texts stay as published.