DEV Community

Samir Chatwiti
Samir Chatwiti

Posted on

Nigeria's e-invoicing mandate: the validation layer generic Peppol tools won't give you

Nigeria's e-invoicing mandate is live. Under the Nigeria Tax Administration Act 2025, the NRS (formerly FIRS) runs a pre-clearance system — the Merchant-Buyer Solution (FIRSMBS) — where every invoice is submitted, validated, and only becomes a legal document once it comes back with an IRN (Invoice Reference Number), a CSID cryptographic stamp and a QR code. Large taxpayers (₦5B+ turnover) have been in since 1 November 2025; the phased rollout continues. Non-compliance costs about ₦1,000,000 up front plus ₦10,000 per day.

The format is Peppol BIS Billing 3.0-based UBL 2.1, so a common integrator reflex is: "I'll run it through a free Peppol validator, green check, ship it."

That reflex produces invoices the NRS rejects.

What the generic tools don't check
Free Peppol/EN 16931 validators stop at the European base layer. The Nigerian profile adds requirements they know nothing about:

Nigerian TIN on both parties — seller and buyer, as PartyTaxScheme/CompanyID or PartyIdentification/ID schemeID='TIN', in the FIRS format.
Per-line VAT — Nigeria expects VAT broken out at line level, not only in the document totals.
NGN expectations and local unit codes.
The IRN / CSID / QR clearance blocks — their presence and structure on cleared documents.
Every one of those is invisible to a vanilla BIS check, and every one of them is a rejection at the NRS.

A real failing invoice
Here's the actual validator output for a UBL invoice missing the buyer's TIN and the IRN block:

{
"valid": false,
"documentType": "380",
"hasIRN": false,
"hasCSID": true,
"hasQR": true,
"errorCount": 2,
"findings": [
{ "level": "error", "ruleId": "NG-RULE-BUYER-TIN",
"text": "Nigerian TIN for the customer (buyer) is missing. NRS requires a Tax Identification Number for both parties (as PartyTaxScheme/CompanyID or PartyIdentification/ID schemeID='TIN').",
"location": "cac:AccountingCustomerParty" },
{ "level": "error", "ruleId": "NG-IRN-ABSENT",
"text": "No Invoice Reference Number (IRN) found. A cleared NRS invoice carries an IRN (cbc:UUID or cac:AdditionalDocumentReference[cbc:ID='IRN']). Structure check only.",
"location": "cbc:UUID / cac:AdditionalDocumentReference" }
]
}
Note the booleans (hasIRN, hasCSID, hasQR): handy for a quick gate — "is this document post-clearance complete?" — without parsing the findings.

One call, the whole stack
The output comes from the Nigeria NRS/FIRS E-Invoice Validator (keyless REST API, free tier available). It layers the checks: official OASIS UBL 2.1 XSD → EN 16931 / Peppol BIS 3.0 base fields and ISO code lists → the Nigeria-specific rules (NG-RULE-) → IRN/CSID/QR structure (NG-IRN-, NG-CSID-, NG-QR-).

curl --request POST \
--url https://nigeria-nrs-firs-e-invoice-validator.p.rapidapi.com/validate \
--header 'Content-Type: application/xml' \
--header 'x-rapidapi-host: nigeria-nrs-firs-e-invoice-validator.p.rapidapi.com' \
--header 'x-rapidapi-key: YOUR_RAPIDAPI_KEY' \
--data-binary @invoice.xml
XML or JSON body, JSON verdict either way, always HTTP 200 with "valid": true|false. Keyless and stateless — no NRS login, no certificate, the invoice is validated in-memory and discarded — so it runs happily in CI or inside an Access Point Provider's onboarding flow.

Honest limits
The CSID is signed by the NRS itself; verifying its cryptographic authenticity would require the NRS signing certificate, which nobody outside the authority holds. So IRN/CSID/QR checks are structural — present and well-formed. valid: true is a pre-clearance readiness signal, not proof of clearance.

That's exactly what you want in CI though: catch the TIN you forgot, the per-line VAT you flattened, the IRN block your serializer dropped — before the NRS does, and before the daily penalties start.

I maintain validators like this for five mandated e-invoicing regimes (Malaysia, Dominican Republic, Nigeria, Bolivia, Costa Rica) — overview with links: github.com/SamirChatwiti/e-invoice-validators.

Top comments (0)