ZATCA Phase 2 API Integration Guide for Developers
ZATCA Phase 2 (the Integration Phase) requires businesses to submit e-invoices to the FATOORA platform in real time. Unlike Phase 1 which only required generating invoices locally, Phase 2 demands cryptographic signing, UBL 2.1 XML compliance, and API integration with ZATCA's servers.
This guide walks through every step of the integration — from CSR generation to production submission — with code examples. If you want to skip the complexity entirely, Jibrid handles all of this through a single API call.
1. Understanding the Architecture
ZATCA Phase 2 follows a three-step flow for every invoice:
- Generate UBL 2.1 XML with all required fields
- Sign the XML using your private key and embed the signature
- Submit to ZATCA's FATOORA API (clearance for B2B, reporting for B2C)
B2B invoices require clearance — ZATCA validates and returns the invoice before you can issue it to the buyer. B2C invoices use reporting — you issue the invoice immediately and report it to ZATCA within 24 hours.
2. CSR Generation
Before you can onboard with ZATCA, you need to generate a Certificate Signing Request (CSR). This is an ECDSA key pair using the secp256k1 curve.
# Generate private key openssl ecparam -name secp256k1 -genkey -noout -out private-key.pem # Generate CSR with ZATCA-required fields openssl req -new -sha256 -key private-key.pem \ -out taxpayer.csr \ -subj "/C=SA/OU=YOUR_VAT_NUMBER/O=YOUR_COMPANY/CN=YOUR_DEVICE_NAME" \ -addext "subjectAltName=DNS:YOUR_EGS_SERIAL_NUMBER" \ -addext "certificateTemplateName=ZATCA-Code-Signing" \ -addext "dirName=2.5.4.97=YOUR_VAT_NUMBER"
The CSR must include your VAT registration number, an EGS (e-invoicing generation solution) serial number, and the ZATCA certificate template extension.
3. CSID Onboarding
Submit your CSR to ZATCA's compliance API to receive a Compliance CSID (CCSID). Then run compliance checks, and finally request a Production CSID (PCSID).
// Step 1: Get Compliance CSID
const ccsidRes = await fetch(
'https://gw-fatoora.zatca.gov.sa/e-invoicing/developer-portal/compliance',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'OTP': '123456', // from ZATCA portal
'Accept-Version': 'V2',
},
body: JSON.stringify({ csr: base64CSR }),
}
);
const { binarySecurityToken, secret } = await ccsidRes.json();
// Step 2: Run compliance checks (submit 3 test invoices)
// ... standard, simplified, and debit/credit note
// Step 3: Exchange for Production CSID
const pcsidRes = await fetch(
'https://gw-fatoora.zatca.gov.sa/e-invoicing/developer-portal/production/csids',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(binarySecurityToken + ':' + secret)}`,
'Accept-Version': 'V2',
},
body: JSON.stringify({ compliance_request_id: requestId }),
}
);4. Building UBL 2.1 XML
ZATCA requires invoices in UBL 2.1 (Universal Business Language) XML format. The XML must include specific namespaces, invoice type codes, tax categories, and line items.
Key fields in every invoice:
| Field | Description | Example |
|---|---|---|
| cbc:InvoiceTypeCode | 388 (standard) or 383 (simplified) | 388 |
| cbc:DocumentCurrencyCode | Always SAR | SAR |
| cac:TaxTotal | VAT amounts per category | 15% standard rate |
| cac:InvoiceLine | Line items with quantity and price | 1x Widget @ 100 SAR |
| cbc:IssueDate | ISO 8601 date | 2026-02-20 |
The XML structure is verbose — a minimal valid invoice is typically 150+ lines. This is one of the biggest pain points in ZATCA integration.
5. Cryptographic Signing
Every invoice must be signed using your production certificate. The signing process involves:
- Canonicalize the XML using C14N (Canonical XML)
- Hash the canonicalized XML with SHA-256
- Sign the hash with your ECDSA private key
- Embed the signature as a UBL extension (XAdES-BES)
import { createSign, createHash } from 'crypto';
import { DOMParser, XMLSerializer } from 'xmldom';
// 1. Canonicalize invoice XML (C14N)
const canonicalized = canonicalize(invoiceXml);
// 2. Hash with SHA-256
const hash = createHash('sha256')
.update(canonicalized)
.digest('base64');
// 3. Sign with ECDSA private key
const sign = createSign('SHA256');
sign.update(canonicalized);
const signature = sign.sign(privateKey, 'base64');
// 4. Embed XAdES-BES signature in UBL extensions
const signedXml = embedSignature(invoiceXml, {
digestValue: hash,
signatureValue: signature,
certificate: productionCert,
});6. QR Code Generation
Simplified (B2C) invoices require a QR code containing a TLV-encoded (Tag-Length-Value) payload with:
- Seller name
- VAT registration number
- Invoice timestamp (ISO 8601)
- Total with VAT
- VAT amount
- XML hash (SHA-256)
- ECDSA signature
- Public key
function encodeTLV(tag: number, value: string | Buffer): Buffer {
const tagBuf = Buffer.from([tag]);
const valBuf = typeof value === 'string'
? Buffer.from(value, 'utf-8')
: value;
const lenBuf = Buffer.from([valBuf.length]);
return Buffer.concat([tagBuf, lenBuf, valBuf]);
}
const qrPayload = Buffer.concat([
encodeTLV(1, sellerName),
encodeTLV(2, vatNumber),
encodeTLV(3, timestamp),
encodeTLV(4, totalWithVat),
encodeTLV(5, vatAmount),
encodeTLV(6, xmlHash),
encodeTLV(7, ecdsaSignature),
encodeTLV(8, publicKey),
]);
const qrBase64 = qrPayload.toString('base64');7. Submitting to ZATCA
With your signed XML and production CSID, submit the invoice to the FATOORA API:
// B2C: Reporting endpoint
const response = await fetch(
'https://gw-fatoora.zatca.gov.sa/e-invoicing/developer-portal/invoices/reporting/single',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${credentials}`,
'Accept-Version': 'V2',
'Accept-Language': 'en',
},
body: JSON.stringify({
invoiceHash: invoiceHash,
uuid: invoiceUuid,
invoice: base64SignedXml,
}),
}
);
// B2B: Clearance endpoint
// Same structure, different URL:
// .../invoices/clearance/single8. The Easier Way: Jibrid
Everything above — CSR generation, CSID onboarding, XML building, signing, QR codes, and submission — is handled by Jibrid in a single API call:
curl -X POST https://api.jibrid.com/v1/zatca/invoices \
-H "Authorization: Bearer jbr_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"invoice_type": "simplified",
"customer": { "name": "Ahmed", "vat_number": "300000000000003" },
"items": [
{ "name": "Consulting", "quantity": 1, "unit_price": 1000, "tax_rate": 15 }
]
}'Jibrid builds the XML, signs it cryptographically, generates the QR code, and submits to ZATCA — returning a clean JSON response with the result.
Skip the XML. Ship faster.
All connectors are free during beta. Get your API key in 30 seconds.
Get your API key →