Country-compliant tax invoices: UAE FTA vs Saudi ZATCA requirements

A developer-focused comparison of UAE FTA and Saudi ZATCA e-invoicing rules, with practical guidance on generating compliant PDFs programmatically.

pdfinvoicestax-compliancezatcauae-ftanode.js

Country-compliant tax invoices: UAE FTA vs Saudi ZATCA requirements

If you build billing systems for the Gulf region, you eventually hit two very different tax invoice regimes: the UAE's Federal Tax Authority (FTA) rules and Saudi Arabia's ZATCA e-invoicing mandate. They look similar on the surface — both are 5% (mostly) VAT jurisdictions, both require bilingual invoices, both want QR codes. But the technical requirements diverge sharply once you start implementing them.

This post breaks down what each authority actually requires from a PDF generation standpoint, and what your code needs to output.

The high-level difference

UAE FTA treats the tax invoice mainly as a document format requirement: include the right fields, in Arabic and English, keep records for 5 years. There's no mandatory real-time clearance.

ZATCA (Phase 2, "Integration Phase") is stricter. Every B2B invoice must be:

  1. Generated as a signed XML (UBL 2.1)
  2. Cleared or reported through ZATCA's API before being sent to the buyer
  3. Accompanied by a human-readable PDF/A-3 with the signed XML embedded
  4. QR-coded with a TLV-encoded, cryptographically-signed payload

So for UAE, you generate a compliant PDF. For Saudi, you generate a compliant PDF plus you interact with a government API and embed signed XML.

UAE FTA requirements

A UAE tax invoice must include:

  • The words "Tax Invoice" clearly displayed
  • Supplier name, address, and TRN (Tax Registration Number)
  • Buyer name, address, and TRN (for B2B)
  • Sequential invoice number
  • Date of issue and date of supply (if different)
  • Description of goods/services
  • Unit price, quantity, discount, taxable amount (per line)
  • VAT rate and VAT amount per line
  • Total amount payable and total VAT — in AED
  • If invoiced in another currency, the exchange rate and AED equivalent

A simplified tax invoice (B2C under AED 10,000) is allowed with fewer fields.

There's no mandatory QR code for UAE (yet), though many vendors add one for parity with ZATCA workflows.

Example minimal data shape:

json
{ "type": "tax_invoice", "invoiceNumber": "INV-2024-0001", "issueDate": "2024-11-15", "supplyDate": "2024-11-15", "supplier": { "name": "Acme Trading LLC", "trn": "100123456700003", "address": "Office 12, Business Bay, Dubai, UAE" }, "buyer": { "name": "Client FZ-LLC", "trn": "100987654300003", "address": "DIC, Dubai, UAE" }, "currency": "AED", "lines": [ { "description": "Consulting services", "quantity": 10, "unitPrice": 500.0, "vatRate": 0.05, "vatAmount": 250.0, "lineTotal": 5250.0 } ], "totals": { "subtotal": 5000.0, "vat": 250.0, "total": 5250.0 } }

ZATCA (Saudi Arabia) requirements

ZATCA Phase 2 is more involved. Every invoice must carry all UAE-equivalent fields plus:

  • Seller VAT group number (if applicable)
  • Buyer VAT registration number (for standard tax invoices)
  • ICV (Invoice Counter Value) and PIH (Previous Invoice Hash) chained across invoices
  • UUID per invoice
  • A QR code with Base64-encoded TLV data containing: seller name, VAT number, timestamp, invoice total, VAT total, XML hash, ECDSA signature, and public key
  • Cryptographic stamp using a certificate issued by ZATCA

The QR TLV structure looks like this:

txt
Tag 1: Seller name Tag 2: VAT registration number Tag 3: Timestamp (ISO 8601) Tag 4: Invoice total (with VAT) Tag 5: VAT total Tag 6: XML invoice hash (Base64 SHA-256) Tag 7: ECDSA signature Tag 8: ECDSA public key Tag 9: ECDSA signature of the stamp

Building the QR payload in Node:

ts
function tlv(tag: number, value: Buffer): Buffer { const len = Buffer.from([value.length]); const t = Buffer.from([tag]); return Buffer.concat([t, len, value]); } const payload = Buffer.concat([ tlv(1, Buffer.from(sellerName, "utf8")), tlv(2, Buffer.from(vatNumber, "utf8")), tlv(3, Buffer.from(timestamp, "utf8")), tlv(4, Buffer.from(totalWithVat, "utf8")), tlv(5, Buffer.from(vatTotal, "utf8")), ]); const qrBase64 = payload.toString("base64");

For Phase 2 you'll also add tags 6–9 after signing the UBL XML.

Clearance vs reporting

ZATCA distinguishes two flows:

  • Standard tax invoices (B2B): must be cleared by ZATCA before being sent to the buyer. Your system posts the signed XML to the API and receives a cleared version with the government stamp.
  • Simplified tax invoices (B2C): are reported to