Software Development

Invoicing in Saudi Arabia: ZATCA E-Invoicing for Developers

AzizWares 8 min read
## What Is ZATCA E-Invoicing? Saudi Arabia's Zakat, Tax and Customs Authority (ZATCA) mandates electronic invoicing for all VAT-registered businesses. The rollout has two phases: **Phase 1 — Generation (Dec 2021):** Invoices must be generated and stored electronically in a structured format. No more handwritten or image-based invoices. A QR code is required on simplified (B2C) invoices. **Phase 2 — Integration (Jan 2023+):** Invoices must be reported to (standard/B2B) or cleared by (simplified/B2C) ZATCA's Fatoora platform in near real-time via API. Cryptographic stamping and XML submission become mandatory in waves. If you're building invoicing software for the Saudi market, you need to handle both phases. Official reference: [ZATCA E-Invoicing Developer Portal](https://zatca.gov.sa/en/E-Invoicing/Pages/default.aspx) ## Technical Requirements Every ZATCA-compliant invoice must satisfy: | Requirement | Detail | |---|---| | **Format** | UBL 2.1 XML (Universal Business Language) | | **UUID** | Each invoice needs a unique UUID v4 | | **Sequential numbering** | Invoice Counter Value (ICV), monotonically increasing | | **Previous invoice hash** | SHA-256 hash chain linking each invoice to the previous one | | **QR Code** | TLV-encoded, Base64 string (Phase 1: 5 fields, Phase 2: 8 fields) | | **Digital signature** | ECDSA with secp256k1 curve, X.509 certificate from ZATCA | | **Cryptographic stamp** | ZATCA returns a signed stamp on clearance | ## QR Code Structure (TLV Encoding) The QR code is a Base64-encoded byte array using Tag-Length-Value format: | Tag | Field | Example | |---|---|---| | 1 | Seller name | `"AzizWares"` | | 2 | VAT registration number | `"300000000000003"` | | 3 | Timestamp (ISO 8601) | `"2026-02-27T12:00:00Z"` | | 4 | Invoice total (with VAT) | `"115.00"` | | 5 | VAT amount | `"15.00"` | | 6 | XML hash (SHA-256) | *(hex bytes)* | | 7 | ECDSA signature | *(DER bytes)* | | 8 | Public key | *(DER bytes)* | Tags 1–5 are required for Phase 1. Tags 6–8 are added in Phase 2. ## Code Example: TLV Encoding in Python ```python import base64 from datetime import datetime def tlv_encode(tag: int, value: str | bytes) -> bytes: if isinstance(value, str): value = value.encode('utf-8') # Note: single-byte length works for tags 1-5 (all < 256 bytes). # For Phase 2 tags (signature/key), use proper ASN.1-style length encoding. if len(value) > 127: length_bytes = len(value).to_bytes(2, 'big') return bytes([tag, 0x82]) + length_bytes + value return bytes([tag, len(value)]) + value def generate_qr_base64( seller: str, vat_number: str, timestamp: datetime, total: str, vat_amount: str, ) -> str: tlv = b'' tlv += tlv_encode(1, seller) tlv += tlv_encode(2, vat_number) tlv += tlv_encode(3, timestamp.isoformat() + 'Z') tlv += tlv_encode(4, total) tlv += tlv_encode(5, vat_amount) return base64.b64encode(tlv).decode('ascii') qr = generate_qr_base64( seller="AzizWares", vat_number="300000000000003", timestamp=datetime(2026, 2, 27, 12, 0, 0), total="115.00", vat_amount="15.00", ) print(qr) ``` ## Dart Equivalent (for Flutter Apps) ```dart import 'dart:convert'; import 'dart:typed_data'; Uint8List tlvEncode(int tag, String value) { final bytes = utf8.encode(value); return Uint8List.fromList([tag, bytes.length, ...bytes]); } String generateZatcaQr({ required String seller, required String vatNumber, required String timestamp, required String total, required String vatAmount, }) { final tlv = BytesBuilder(); tlv.add(tlvEncode(1, seller)); tlv.add(tlvEncode(2, vatNumber)); tlv.add(tlvEncode(3, timestamp)); tlv.add(tlvEncode(4, total)); tlv.add(tlvEncode(5, vatAmount)); return base64Encode(tlv.toBytes()); } ``` ## Common Developer Pitfalls **1. Timestamp format:** ZATCA requires ISO 8601 with timezone. Using `2026-02-27 12:00:00` instead of `2026-02-27T12:00:00Z` will cause rejection in Phase 2. **2. Arabic text in TLV:** The seller name must be UTF-8 encoded. If your seller name is `"شركة عزيز"`, the byte length in the TLV `L` field must reflect the UTF-8 byte count (which is longer than the character count). Off-by-one here corrupts the entire QR payload. **3. Sandbox vs production:** ZATCA's sandbox (`gw-fatoora.zatca.gov.sa`) uses different compliance certificates than production. Your sandbox CSR won't work in production — you must onboard with a real Hardware Security Module (HSM) or ZATCA-approved solution. **4. Hash chain breaks:** Each invoice's hash depends on the previous invoice's hash. If you regenerate or skip an invoice, the chain breaks and the entire batch fails validation. **5. Certificate renewal:** ZATCA compliance certificates expire. Build renewal logic into your system — don't discover this in production. ## How Mufawtir Handles This [Mufawtir](/apps/mufawtir/) is AzizWares' invoicing app built specifically for ZATCA compliance. It handles TLV-encoded QR generation, sequential numbering, and ZATCA-compliant invoice formatting out of the box — so you don't have to build the plumbing yourself. If you need a ready-made solution instead of building from scratch, [check it out](/apps/mufawtir/). ## FAQ **Q: Do I need Phase 2 compliance right now?** A: ZATCA is rolling out Phase 2 in waves based on revenue thresholds. Check ZATCA's official wave schedule — but building for Phase 2 from the start saves you a painful migration. **Q: Can I use JSON instead of XML?** A: No. ZATCA mandates UBL 2.1 XML. The Fatoora API only accepts XML payloads. You can use JSON internally, but the submission must be XML. **Q: What QR code library should I use?** A: Any library that generates standard QR codes works — `qrcode` in Python, `qr_flutter` in Dart. The ZATCA-specific part is the *payload* (TLV-encoded Base64 string), not the QR rendering itself. **Q: Is the ZATCA sandbox reliable for testing?** A: It's functional but can be slow and occasionally down. Always build retry logic and don't rely on it for CI/CD pipelines. Cache valid responses for local development. **Q: Do freelancers need ZATCA e-invoicing?** A: If you're VAT-registered in Saudi Arabia (revenue above SAR 375,000), yes. The threshold applies regardless of business size.
Tags: ZATCA E-Invoicing Saudi Arabia QR Code TLV UBL XML Python Dart Flutter VAT Fatoora Mufawtir