Community Tutorials ZATCA Technical ZATCA Phase 2 Technical Integration — UBL 2.1, XAdES, B2B Clearance
ZATCA Phase 2 Technical Integration — UBL 2.1, XAdES, B2B Clearance
ZATCA TECHNICAL

ZATCA Phase 2 Technical Integration — UBL 2.1, XAdES, B2B Clearance

SKYLINE Knowledge Base
Photo by Scott Graham on Unsplash

A practitioner-grade walk-through of ZATCA Phase 2 Technical Integration — UBL 2.1, XAdES, B2B Clearance. Scope, controls, implementation phases and audit-ready evidence — with sample policies and configs you can adapt for ZATCA Technical.

Overview

ZATCA Phase 2 — also called the Integration Phase or Fatoora — moves Saudi e-invoicing from offline-generated PDFs into a real-time, cryptographically-signed, government-cleared workflow. Every standard tax invoice (B2B) must be cleared by ZATCA before it is delivered to the buyer; every simplified invoice (B2C) must be reported within 24 hours. The technical heart is UBL 2.1 XML, XAdES B-B signatures, and the Fatoora API. Failure to clear means the invoice has no legal value.

Who this applies to

  • Every VAT-registered taxpayer in KSA.
  • Roll-in is in waves: largest taxpayers first, smaller ones in subsequent waves, with a four-month notice per wave.
  • Exemptions: non-resident VAT-payers and a narrow list of exempt sectors.

Architecture overview

[ERP] → invoice generator → UBL XML → XAdES sign with CSID → POST /clearance
                                                              ↓
                                                      [ZATCA Fatoora]
                                                              ↓
                                                  signed XML returned
                                                  + QR + cleared-marker
                                                              ↓
                                                  PDF/A-3 to buyer
                                                  XML archived 6 years

Step 1: UBL 2.1 invoice structure

ZATCA mandates a constrained UBL 2.1 profile (PINT-KSA). Minimum essential elements:

<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
         xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
         xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
  <cbc:ProfileID>reporting:1.0</cbc:ProfileID>
  <cbc:ID>INV-2026-00187</cbc:ID>
  <cbc:UUID>3cf5ee18-ee25-44ea-a444-2c37ba7f28be</cbc:UUID>
  <cbc:IssueDate>2026-05-27</cbc:IssueDate>
  <cbc:IssueTime>14:25:43</cbc:IssueTime>
  <cbc:InvoiceTypeCode name="0100000">388</cbc:InvoiceTypeCode>
  <cbc:DocumentCurrencyCode>SAR</cbc:DocumentCurrencyCode>
  <cbc:TaxCurrencyCode>SAR</cbc:TaxCurrencyCode>
  <cac:AdditionalDocumentReference>
    <cbc:ID>ICV</cbc:ID>
    <cbc:UUID>187</cbc:UUID>
  </cac:AdditionalDocumentReference>
  <cac:AdditionalDocumentReference>
    <cbc:ID>PIH</cbc:ID>
    <cac:Attachment>
      <cbc:EmbeddedDocumentBinaryObject mimeCode="text/plain">
        NWZl...prev-invoice-hash
      </cbc:EmbeddedDocumentBinaryObject>
    </cac:Attachment>
  </cac:AdditionalDocumentReference>
  <!-- Seller, buyer, lines, tax breakdown ... -->
</Invoice>

Two values are critical and easy to get wrong:

  • ICV (Invoice Counter Value) — monotonically increasing per CSID. Reset only by ZATCA.
  • PIH (Previous Invoice Hash) — base64 of SHA-256 of the previous invoice's signed XML. Without this, the chain breaks.

Step 2: XAdES B-B signing

The signature is XAdES Basic with attached signature properties:

<ext:UBLExtensions>
  <ext:UBLExtension>
    <ext:ExtensionURI>urn:oasis:names:specification:ubl:dsig:enveloped:xades</ext:ExtensionURI>
    <ext:ExtensionContent>
      <sig:UBLDocumentSignatures>
        <sac:SignatureInformation>
          <ds:Signature Id="signature">
            <ds:SignedInfo>
              <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2006/12/xml-c14n11"/>
              <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
              <!-- references, transforms ... -->
            </ds:SignedInfo>
            <ds:SignatureValue>BASE64...</ds:SignatureValue>
            <ds:KeyInfo>
              <ds:X509Data>
                <ds:X509Certificate>BASE64 CSID...</ds:X509Certificate>
              </ds:X509Data>
            </ds:KeyInfo>
          </ds:Signature>
        </sac:SignatureInformation>
      </sig:UBLDocumentSignatures>
    </ext:ExtensionContent>
  </ext:UBLExtension>
</ext:UBLExtensions>

Use ECDSA on secp256k1 with SHA-256 hash. Sign the canonicalised XML, then embed the signature back.

Step 3: Fatoora API endpoints

Sandbox: https://gw-fatoora.zatca.gov.sa/e-invoicing/developer-portal/ Production: https://gw-fatoora.zatca.gov.sa/e-invoicing/core/

POST /invoices/clearance/single        ← standard B2B
POST /invoices/reporting/single        ← simplified B2C
POST /compliance/csids                 ← CSID onboarding
GET  /compliance/csids/{id}            ← CSID status
POST /compliance/csids/renew           ← CSID renewal

Sample clearance request:

curl -X POST 'https://gw-fatoora.zatca.gov.sa/e-invoicing/core/invoices/clearance/single' \
  -H 'Accept: application/json' \
  -H 'Accept-Language: en' \
  -H 'Accept-Version: V2' \
  -H 'Clearance-Status: 1' \
  -H 'Authorization: Basic <BASE64(csid:csid_secret)>' \
  -H 'Content-Type: application/json' \
  --data '{
    "invoiceHash": "BASE64SHA256",
    "uuid": "3cf5ee18-ee25-44ea-a444-2c37ba7f28be",
    "invoice": "BASE64_UBL_XML"
  }'

Step 4: QR code

For both B2B and B2C the QR (TLV-encoded base64) carries:

  • Seller name.
  • VAT registration number.
  • Timestamp.
  • Invoice total (with VAT).
  • VAT total.
  • Hash of invoice XML.
  • ECDSA signature.
  • Public key.
  • Certificate signature.

For B2B the TLV includes the cryptographic stamp from ZATCA's clearance response.

Step 5: Common error codes

| Code | Meaning | Fix | |---|---|---| | BR-KSA-15 | ICV not monotonic | Persist ICV per CSID per device, single writer | | BR-KSA-26 | PIH does not match previous hash | Persist PIH after every successful submission | | BR-S-08 | Tax category mismatch | Check VAT category mapping in your product master | | ZATCA-INV-008 | XML schema validation failed | Run against the published XSD before sending | | ZATCA-CRT-005 | Signature invalid | Re-sign after canonicalisation, not before |

Common gotchas

  • Generating XML, signing, then re-serialising — the re-serialise changes whitespace and breaks the signature. Sign last.
  • ICV not persisted across restarts — gaps and duplicates appear.
  • Timezone confusion — ZATCA expects Saudi time but ISO 8601 with offset; missing offset fails validation.
  • Buyer VAT number empty for a "registered customer" — auto-rejection.

Verification

  • Schema validation against the published XSD.
  • ICV chain integrity check across the last 1000 invoices.
  • Signature verification against your own CSID.
  • End-to-end test in sandbox with 30 different scenarios (standard, debit note, credit note, etc.).
  • Storage of cleared invoices for 6 years minimum.

Conclusion

ZATCA Phase 2 is not an export feature; it is a real-time integration with the tax authority. Build the integration with idempotency, persistence, and observability — your finance team will thank you, and so will your auditor.

Related guides

SKYLINE Engineering

@skyline

The engineering team at SKYLINE Industrial Solutions. We publish field-tested guides drawn from real KSA and GCC deployments.

See author profile
SKYLINE engineering services

Need this implemented for you?

Reading is free — building it right takes a team. SKYLINE engineers ship ZATCA Technical for Aramco vendors, banks, hospitals and government agencies across Saudi Arabia. Talk to us before you start.

Aramco Approved Contractor ISO 9001 · ISO 27001 SAMA CSF aligned NCA ECC ready 247+ KSA clients

Comments

0 total · 0 threads
Be the first to leave a comment.