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