ZATCA Phase 2 E-Invoicing Integration Guide: Wave-by-Wave Developer Checklist
A technical walkthrough of ZATCA Phase 2 integration requirements, wave timelines, XML/UBL compliance, cryptographic signing, and API onboarding for Saudi e-invoicing.
1. ZATCA E-Invoicing: Phase 1 vs Phase 2
Saudi Arabia's e-invoicing mandate, governed by the Zakat, Tax and Customs Authority (ZATCA), is deployed in two distinct phases.
Phase 1 (Generation Phase), effective December 4, 2021, required all VAT-registered taxpayers to generate e-invoices and e-notes in a structured digital format through a compliant Electronic Invoicing Solution (EGS). Phase 1 was primarily about format: stop issuing handwritten or unstructured invoices, start producing machine-readable documents with mandatory fields, QR codes, and UUIDs. No API connectivity to ZATCA was required. Invoices were generated and stored locally.
Phase 2 (Integration Phase), rolling out in waves since January 1, 2023, is fundamentally different. It requires real-time or near-real-time integration with ZATCA's Fatoora platform via API. Depending on the invoice type, documents must either be reported (transmitted to ZATCA within 24 hours) or cleared (validated and stamped by ZATCA before being shared with the buyer). Phase 2 also introduces cryptographic signing requirements, stricter XML schema validation, and a formal onboarding process involving Certificate Signing Requests (CSRs) and Cryptographic Stamp Identifiers (CSIDs).
If you are building or maintaining an invoicing system for the Saudi market, Phase 2 is where the engineering complexity lives. The rest of this guide focuses on what you need to implement.
2. Wave Schedule: Who Is Affected and When
ZATCA rolls out Phase 2 in waves, each targeting taxpayers based on their taxable turnover during specific calendar years. Taxpayers are notified at least six months before their go-live deadline. Here is the schedule for the most recent waves, which are relevant for businesses coming into compliance now:
| Wave | Go-Live Window | Revenue Threshold | Reference Period |
|---|---|---|---|
| Wave 1 | Jan 1, 2023 | SAR 3 billion+ | 2021 |
| Wave 2 | Jul 1, 2023 | SAR 500 million+ | 2021 |
| Wave 3 | Oct 1, 2023 | SAR 250 million+ | 2021 or 2022 |
| Waves 4-10 | Nov 2023 - Dec 2024 | SAR 150M down to SAR 5M | Various |
| Waves 11-22 | Jan 2025 - Dec 2025 | SAR 4M down to SAR 1M | 2022, 2023, or 2024 |
| Wave 23 | Jan 1 - Mar 31, 2026 | SAR 750,000+ | 2022, 2023, or 2024 |
| Wave 24 | Apr 1 - Jun 30, 2026 | SAR 375,000+ | 2022, 2023, or 2024 |
Note: ZATCA continues to announce new waves periodically. Verify your specific wave assignment on ZATCA's official roll-out page. If your revenue exceeded the threshold in any of the reference calendar years, you are in scope.
The trend is clear: the threshold is dropping with each wave. Virtually all VAT-registered businesses in Saudi Arabia will eventually need Phase 2 integration. If you are a software vendor serving the Saudi market, treating this as a core feature rather than a one-off project is the right approach.
3. Technical Requirements: UBL 2.1 XML Structure
3.1 Format
All Phase 2 e-invoices must be generated in UBL 2.1 XML format, conforming to ZATCA's XML Implementation Standard (itself a constrained profile of ISO EN 16931). Alternatively, a PDF/A-3 with an embedded UBL 2.1 XML payload is accepted, but the XML is the authoritative data; the PDF is the visual representation.
3.2 Invoice Types
ZATCA distinguishes two invoice types, each with different processing flows:
- Standard Tax Invoice (B2B): Issued for business-to-business transactions. Subject to reporting (asynchronous submission to ZATCA within 24 hours).
- Simplified Tax Invoice (B2C): Issued for business-to-consumer transactions. Subject to clearance (synchronous validation by ZATCA before the invoice can be shared with the buyer).
3.3 Mandatory XML Fields
The following fields are mandatory in every Phase 2 XML invoice. This is not exhaustive; consult the ZATCA Data Dictionary for the full specification.
1<?xml version="1.0" encoding="UTF-8"?> 2<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" 3 xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" 4 xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" 5 xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"> 6 7 <!-- UBL Extensions: contains the digital signature --> 8 <ext:UBLExtensions> 9 <ext:UBLExtension> 10 <ext:ExtensionContent> 11 <!-- XMLDSig Signature block inserted here --> 12 </ext:ExtensionContent> 13 </ext:UBLExtension> 14 </ext:UBLExtensions> 15 16 <cbc:ProfileID>reporting:1.0</cbc:ProfileID> 17 <cbc:ID>INV-2026-00042</cbc:ID> 18 <cbc:UUID>6f4d20e0-6bfe-4a80-9389-7dabe6620f12</cbc:UUID> 19 <cbc:IssueDate>2026-04-15</cbc:IssueDate> 20 <cbc:IssueTime>14:30:00</cbc:IssueTime> 21 <cbc:InvoiceTypeCode name="0100000">388</cbc:InvoiceTypeCode> 22 <cbc:DocumentCurrencyCode>SAR</cbc:DocumentCurrencyCode> 23 <cbc:TaxCurrencyCode>SAR</cbc:TaxCurrencyCode> 24 25 <!-- Previous Invoice Hash (for chain integrity) --> 26 <cac:AdditionalDocumentReference> 27 <cbc:ID>PIH</cbc:ID> 28 <cac:Attachment> 29 <cbc:EmbeddedDocumentBinaryObject mimeCode="text/plain"> 30 NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzlj... 31 </cbc:EmbeddedDocumentBinaryObject> 32 </cac:Attachment> 33 </cac:AdditionalDocumentReference> 34 35 <!-- QR Code --> 36 <cac:AdditionalDocumentReference> 37 <cbc:ID>QR</cbc:ID> 38 <cac:Attachment> 39 <cbc:EmbeddedDocumentBinaryObject mimeCode="text/plain"> 40 <!-- Base64-encoded TLV QR data --> 41 </cbc:EmbeddedDocumentBinaryObject> 42 </cac:Attachment> 43 </cac:AdditionalDocumentReference> 44 45 <!-- Seller (AccountingSupplierParty) --> 46 <cac:AccountingSupplierParty> 47 <cac:Party> 48 <cac:PartyIdentification> 49 <cbc:ID schemeID="CRN">1234567890</cbc:ID> 50 </cac:PartyIdentification> 51 <cac:PostalAddress> 52 <cbc:StreetName>King Fahd Road</cbc:StreetName> 53 <cbc:BuildingNumber>1234</cbc:BuildingNumber> 54 <cbc:CityName>Riyadh</cbc:CityName> 55 <cbc:PostalZone>12345</cbc:PostalZone> 56 <cac:Country><cbc:IdentificationCode>SA</cbc:IdentificationCode></cac:Country> 57 </cac:PostalAddress> 58 <cac:PartyTaxScheme> 59 <cbc:CompanyID>300000000000003</cbc:CompanyID> 60 <cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme> 61 </cac:PartyTaxScheme> 62 <cac:PartyLegalEntity> 63 <cbc:RegistrationName>Acme Trading Co</cbc:RegistrationName> 64 </cac:PartyLegalEntity> 65 </cac:Party> 66 </cac:AccountingSupplierParty> 67 68 <!-- Invoice lines, tax totals, monetary totals follow --> 69</Invoice>
Key points:
- UUID must be a valid v4 UUID, unique per invoice. After rejection, you must generate a new UUID; never reuse one.
- InvoiceTypeCode uses a
nameattribute encoding a 7-digit binary flag (e.g.,0100000for a standard tax invoice). - Previous Invoice Hash (PIH) chains each invoice to the previous one, creating a tamper-evident sequence. The first invoice in a chain uses the Base64 encoding of SHA-256 of
0. - Invoice Counter Value (ICV) must be a sequential integer that never decreases and never repeats within an EGS unit.
3.4 QR Code with TLV Encoding
The QR code embedded in Phase 2 invoices uses Tag-Length-Value (TLV) encoding, then Base64-encoded. The required TLV tags are:
| Tag | Field | Type |
|---|---|---|
| 1 | Seller Name | UTF-8 String |
| 2 | VAT Registration Number (TRN) | UTF-8 String |
| 3 | Invoice Timestamp (ISO 8601) | UTF-8 String |
| 4 | Invoice Total (with VAT) | UTF-8 String |
| 5 | VAT Total | UTF-8 String |
| 6 | SHA-256 Hash of XML | Byte Array |
| 7 | ECDSA Signature | Byte Array |
| 8 | Public Key | Byte Array |
| 9 | Certificate Signature (CSID stamp) | Byte Array |
Tags 1-5 were required in Phase 1. Tags 6-9 are Phase 2 additions and are what make the QR code cryptographically verifiable.
1# Pseudocode: TLV encoding for QR 2def tlv_encode(tag: int, value: bytes) -> bytes: 3 return bytes([tag]) + bytes([len(value)]) + value 4 5qr_payload = b"" 6qr_payload += tlv_encode(1, seller_name.encode("utf-8")) 7qr_payload += tlv_encode(2, vat_number.encode("utf-8")) 8qr_payload += tlv_encode(3, timestamp_iso.encode("utf-8")) 9qr_payload += tlv_encode(4, str(total_with_vat).encode("utf-8")) 10qr_payload += tlv_encode(5, str(vat_amount).encode("utf-8")) 11qr_payload += tlv_encode(6, invoice_hash_bytes) 12qr_payload += tlv_encode(7, ecdsa_signature_bytes) 13qr_payload += tlv_encode(8, public_key_bytes) 14qr_payload += tlv_encode(9, certificate_signature_bytes) 15 16qr_base64 = base64.b64encode(qr_payload).decode("ascii")
Important: For values longer than 127 bytes (common for tags 7-9), TLV length encoding uses multiple bytes. Consult the ZATCA technical guidelines for the exact multi-byte length encoding rules.
4. Cryptographic Signing: ECDSA, CSR, and CSID
4.1 Key Generation
ZATCA mandates ECDSA with the secp256k1 elliptic curve for invoice signing. Generate your private key using OpenSSL:
1# Generate EC private key 2openssl ecparam -name secp256k1 -genkey -noout -out ec-secp256k1-priv-key.pem 3 4# Generate a Certificate Signing Request (CSR) 5openssl req -new -sha256 \ 6 -key ec-secp256k1-priv-key.pem \ 7 -out taxpayer.csr \ 8 -subj "/CN=EGS-Unit-01/OU=MyBranch/O=MyCompany/C=SA" \ 9 -addext "subjectAltName=DNS:egs1.mycompany.com,dirName:cn=TST-886431145-399999999900003,serialNumber=1-TST|2-TST|3-ed22f1d8-e6a2-1118-9b58-d9a8f11e445f" \ 10 -addext "certificateTemplateName=ZATCA-Code-Signing" \ 11 -addext "1.3.6.1.4.1.311.20.2=ASN1:UTF8String:TSTZATCA-Code-Signing"
The CSR must contain specific ZATCA-mandated extensions, including:
- Organization Identifier (OID 2.5.4.97): Formatted as
TST-{TIN}-{CRN}for sandbox or the equivalent production prefix. - Certificate Template Name: Must be
ZATCA-Code-Signing. - EGS Serial Number: A pipe-delimited string containing solution name, hardware identifier, and a UUID for the EGS unit.
4.2 Onboarding: Compliance CSID to Production CSID
The onboarding process has three stages:
Stage 1: Obtain a Compliance CSID (CCSID)
Submit your CSR to ZATCA's compliance endpoint. You receive a Compliance CSID (certificate) and a request ID.
1curl -X POST \ 2 https://gw-fatoora.zatca.gov.sa/e-invoicing/core/compliance \ 3 -H "Content-Type: application/json" \ 4 -H "Accept-Version: V2" \ 5 -H "OTP: 123456" \ 6 -d '{ 7 "csr": "<Base64-encoded CSR>" 8 }'
The OTP header contains a one-time password provided by ZATCA during the registration process. The response includes binarySecurityToken (your CCSID certificate, Base64-encoded), secret (your API password), and requestID.
Stage 2: Pass Compliance Checks
Using the CCSID credentials, submit sample invoices for compliance validation:
1curl -X POST \ 2 https://gw-fatoora.zatca.gov.sa/e-invoicing/core/compliance/invoices \ 3 -H "Content-Type: application/json" \ 4 -H "Accept-Version: V2" \ 5 -H "Accept-Language: en" \ 6 -u "<CCSID-Base64>:<secret>" \ 7 -d '{ 8 "invoiceHash": "<SHA-256 hash of the invoice XML>", 9 "uuid": "<invoice UUID>", 10 "invoice": "<Base64-encoded signed XML>" 11 }'
You must pass compliance checks for all invoice subtypes your EGS will handle: standard invoices, simplified invoices, debit notes, credit notes.
Stage 3: Obtain a Production CSID (PCSID)
Once compliance checks pass, exchange your CCSID for a Production CSID:
1curl -X POST \ 2 https://gw-fatoora.zatca.gov.sa/e-invoicing/core/production/csids \ 3 -H "Content-Type: application/json" \ 4 -H "Accept-Version: V2" \ 5 -u "<CCSID-Base64>:<secret>" \ 6 -d '{ 7 "compliance_request_id": "<requestID from Stage 1>" 8 }'
The Production CSID is what your EGS uses for live invoice submission. It has an expiration date; plan for renewal before it expires.
4.3 Invoice Signing Flow
For each invoice your system generates:
- Build the unsigned XML invoice with all mandatory fields
- Compute the SHA-256 hash of the canonicalized XML
- Sign the hash using ECDSA with your private key
- Embed the XMLDSig signature in the
UBLExtensionselement - Compute the QR code TLV payload including the signature and hash
- Set the Previous Invoice Hash to the hash of the preceding invoice
- Increment the Invoice Counter Value
5. API Integration: Reporting vs Clearance
With your Production CSID in hand, the submission flow depends on the invoice type.
5.1 Reporting (Standard / B2B Invoices)
Standard tax invoices are reported asynchronously. You submit the signed invoice to ZATCA, and ZATCA returns a validation result. The invoice is valid and can be shared with the buyer regardless of the reporting result, but non-compliance triggers penalties.
1curl -X POST \ 2 https://gw-fatoora.zatca.gov.sa/e-invoicing/core/invoices/reporting/single \ 3 -H "Content-Type: application/json" \ 4 -H "Accept-Version: V2" \ 5 -H "Accept-Language: en" \ 6 -H "Clearance-Status: 0" \ 7 -u "<PCSID-Base64>:<secret>" \ 8 -d '{ 9 "invoiceHash": "<SHA-256 hash>", 10 "uuid": "<invoice UUID>", 11 "invoice": "<Base64-encoded signed XML>" 12 }'
The Clearance-Status: 0 header explicitly indicates this is a reporting request.
5.2 Clearance (Simplified / B2C Invoices)
Simplified invoices require clearance: ZATCA validates the invoice, stamps it, and returns a cleared version. You must use the ZATCA-returned XML (not your original) as the fiscally valid document.
1curl -X POST \ 2 https://gw-fatoora.zatca.gov.sa/e-invoicing/core/invoices/clearance/single \ 3 -H "Content-Type: application/json" \ 4 -H "Accept-Version: V2" \ 5 -H "Accept-Language: en" \ 6 -H "Clearance-Status: 1" \ 7 -u "<PCSID-Base64>:<secret>" \ 8 -d '{ 9 "invoiceHash": "<SHA-256 hash>", 10 "uuid": "<invoice UUID>", 11 "invoice": "<Base64-encoded signed XML>" 12 }'
The response includes clearedInvoice (the ZATCA-stamped XML, Base64-encoded) and a validationResults object. Always store and use the cleared version.
5.3 Handling Responses
ZATCA's API returns a validationResults object containing:
- status:
PASS,WARNING, orERROR - infoMessages, warningMessages, errorMessages: arrays with
type,code,category,message, andstatusfields
A WARNING status means the invoice was accepted but has issues you should fix. An ERROR status means the invoice was rejected. On rejection, you must fix the issue, generate a new UUID, and resubmit.
6. Common Validation Errors and How to Fix Them
These are the errors developers encounter most frequently during integration:
| Error | Cause | Fix |
|---|---|---|
Invalid Invoice Hash | Hash computed over non-canonicalized XML, or hash of the wrong XML version (pre-signature vs post-signature mismatch) | Hash must be computed on the invoice XML before the UBLExtensions/Signature block is inserted. Use exclusive XML canonicalization (C14N). |
Invalid CSR | CSR missing required ZATCA extensions (OID, template name, serial number) | Regenerate the CSR with all mandatory extensions. Double-check the Organization Identifier format. |
UUID already exists | Reusing a UUID from a previously submitted or rejected invoice | Generate a fresh v4 UUID for every submission attempt, including resubmissions after rejection. |
ICV sequence broken | Invoice Counter Value is not strictly sequential or has gaps | Ensure your ICV is an atomically incrementing counter per EGS unit. Never skip or reuse values. |
PIH mismatch | Previous Invoice Hash does not match the hash ZATCA has on record for the preceding invoice | Your system must store the hash of each successfully submitted invoice and use it as the PIH for the next one. If your chain breaks, you may need to contact ZATCA support. |
Invalid signature | Signature was computed with the wrong key, or the signed content does not match the submitted XML | Verify that you are signing with the private key corresponding to your CSID certificate. Ensure no whitespace or encoding changes occur between signing and submission. |
Missing mandatory field | A required XML element is absent or empty | Cross-reference every field against ZATCA's Data Dictionary. Common omissions: IssueTime, TaxCurrencyCode, BuildingNumber in the address. |
Certificate expired | Production CSID has passed its expiration date | Implement CSID renewal before expiry. Monitor the notAfter field in your certificate. |
7. Testing with the ZATCA Sandbox
ZATCA provides a sandbox environment that mirrors the production API. The sandbox base URL is:
https://gw-fatoora.zatca.gov.sa/e-invoicing/developer-portal
The sandbox supports the full onboarding flow (CSR submission, CCSID issuance, compliance checks, PCSID issuance) and both reporting and clearance submission. Use the OTP value 123456 for sandbox onboarding.
Testing checklist:
- Generate a key pair and CSR with all required extensions
- Submit the CSR to the sandbox compliance endpoint; verify you receive a CCSID
- Build and sign a sample standard invoice XML; submit for compliance check
- Build and sign a sample simplified invoice XML; submit for compliance check
- Build and sign credit notes and debit notes; submit for compliance check
- Request a Production CSID using the sandbox endpoint
- Submit a standard invoice via the reporting endpoint; verify
PASSresponse - Submit a simplified invoice via the clearance endpoint; verify you receive a
clearedInvoice - Deliberately submit an invalid invoice (bad hash, missing field) and verify the error response matches your error-handling logic
- Test the invoice chain: submit 10+ sequential invoices and verify PIH and ICV integrity
Tip: ZATCA's Developer Portal also provides a web-based XML validator and an SDK. Use the validator to debug XML structure issues before testing via API.
8. Factur-X, Peppol, and Cross-Border Context
If your business or your clients operate across borders, understanding how ZATCA's approach fits within the global e-invoicing landscape is valuable.
8.1 Factur-X / ZUGFeRD
Factur-X (France) and ZUGFeRD (Germany) are the same standard under two names. They define a hybrid invoice format: a human-readable PDF with an embedded structured XML payload using the UN/CEFACT Cross Industry Invoice (CII) format. This is fundamentally different from ZATCA's approach:
- ZATCA uses UBL 2.1 XML as the authoritative format, with optional PDF/A-3 wrapping
- Factur-X uses CII XML embedded in PDF/A-3, with the PDF as the primary delivery format
- ZATCA requires real-time API integration with the tax authority
- Factur-X (as of 2026) does not require real-time clearance; France's Plateforme de Dematérialisation Partenaire (PDP) model is still being rolled out
Both standards are subsets of EN 16931, the European semantic standard for e-invoicing. If you are building a multi-country compliance system, EN 16931 is the common denominator.
8.2 Peppol
Peppol is not a format; it is a delivery network. Invoices on Peppol use the Peppol BIS Billing 3.0 format, which is based on UBL 2.1 (the same base standard as ZATCA, though with different constraints and extensions). Peppol defines how invoices are routed from sender to receiver through certified Access Points.
Key differences from ZATCA:
- Peppol is a B2B network; there is no tax authority clearance model
- Peppol uses 4-corner routing (sender Access Point to receiver Access Point); ZATCA uses a 2-corner model (taxpayer to ZATCA)
- Peppol is mandatory for B2G in many EU countries and increasingly for B2B (Belgium mandates it from 2026)
8.3 Practical Implications
If you serve clients who invoice both in Saudi Arabia and in the EU, your system needs to handle:
- UBL 2.1 XML generation (shared between ZATCA and Peppol, though the profiles differ)
- CII XML generation (for Factur-X/ZUGFeRD markets)
- ZATCA-specific signing and API submission
- Peppol-specific Access Point connectivity
- Different validation rule sets per jurisdiction
The good architectural decision is to separate concerns: build a core invoice data model conforming to EN 16931 semantics, then implement jurisdiction-specific serializers and submission adapters.
9. Production Go-Live Checklist
Before your EGS goes live for a specific taxpayer, verify every item on this list:
- ZATCA registration confirmed. The taxpayer has been notified of their wave assignment and go-live deadline.
- EGS unit registered. Each physical or virtual invoicing unit has its own CSR, CSID, and sequential ICV counter.
- Production CSID obtained. You have exchanged the Compliance CSID for a Production CSID via the production API (not sandbox).
- CSID renewal scheduled. You have automated or calendared renewal before the certificate's
notAfterdate. - Invoice chain initialized. The first invoice's PIH is set to the Base64 of SHA-256(
0). Subsequent invoices reference the hash of the prior invoice. - All invoice types tested. Standard invoices, simplified invoices, credit notes, and debit notes have all passed compliance checks.
- QR code generation verified. TLV-encoded QR codes are readable and contain all 9 tags. Test with ZATCA's mobile verification app.
- Error handling implemented. Your system correctly handles
WARNINGandERRORresponses, generates new UUIDs on rejection, and does not break the ICV/PIH chain. - Clearance flow tested. For simplified invoices, your system stores and uses the ZATCA-returned
clearedInvoiceXML, not the original submission. - Retry logic in place. Network failures, timeouts, and 5xx responses from ZATCA's API are handled with exponential backoff. Invoices are queued and retried without data loss.
- Audit trail maintained. Every submission attempt, response, UUID, ICV, hash, and CSID used is logged for compliance auditing.
- XML schema validation. Your XML output passes validation against ZATCA's published XSD schemas before submission.
- Address fields complete. Saudi addresses require
StreetName,BuildingNumber,CityName,PostalZone, andCountryCode. Missing any of these is a common rejection cause. - VAT calculations verified. Line-level and document-level VAT totals are consistent. Rounding follows ZATCA's prescribed rules (2 decimal places for SAR amounts).
10. Need Help With Integration?
If you are building ZATCA Phase 2 compliance into your software, or if your business needs to become compliant without replacing your existing invoicing system, I can help.
I have built and shipped ZATCA Phase 2 integration for production systems, including the full pipeline from document extraction through cryptographic signing to API submission and clearance. I work with the specific edge cases that generic solutions miss: construction progress billing, multi-entity consolidation, legacy system wrappers, and custom ERP integrations.
I also cover Factur-X, Peppol, and XRechnung compliance for businesses operating across Saudi and European markets.
Learn more about my e-invoicing consulting services, or book a call to discuss your specific requirements.
This guide reflects ZATCA regulations and wave schedules as of April 2026. ZATCA updates its requirements periodically. Always verify current wave assignments and technical specifications against ZATCA's official e-invoicing portal and the Fatoora Developer Community.