Hello, tech enthusiasts! 👋
Your manager says: “Our billing system must support Saudi e-invoicing.” You search online and find words like Fatoora, clearance, CSID, UBL and hash chain. It feels like a new language!
Do not worry. Today we explain each word simply and write a little real code. This is a technical guide, not legal or tax advice. Rules change, so always check the official site of the Zakat, Tax and Customs Authority (ZATCA) at https://zatca.gov.sa before you build.
What you need
- Basic knowledge of invoices (seller, buyer, VAT, total)
- Python 3 for the code example
- An account on the ZATCA developer portal (linked from the official site)
Step 1: Understand the two phases
The Saudi e-invoicing program is called Fatoora. It came in two phases.
- Phase 1, “Generation” (started December 2021). Businesses must create invoices with a computer system, not by hand. Simplified invoices need a QR code. The invoice does not go to ZATCA yet.
- Phase 2, “Integration” (started January 2023). Your system connects to ZATCA’s platform and sends invoices to it. Businesses join in waves, and ZATCA tells each group its date.
Step 2: Know the two invoice types
- Standard tax invoice: for business to business (B2B). The buyer is a registered company.
- Simplified tax invoice: for business to customer (B2C), like a shop receipt.
In Phase 2 they travel in different ways:
- Clearance (standard invoices): you send the invoice to ZATCA first. ZATCA checks it and adds its stamp. Only then do you give it to the buyer. Like passport control: no stamp, no entry.
- Reporting (simplified invoices): you give the invoice to the customer right away, and then report it to ZATCA within 24 hours.
Step 3: Learn the invoice format
In Phase 2, the official invoice is an XML file in the UBL 2.1 format. XML is text with labels around each value, like <Amount>100.00</Amount>. UBL is an international standard for those labels, and ZATCA adds its own rules. You can give the customer a PDF, but for Phase 2 it should be a PDF/A-3 file with the XML inside it.
Each invoice also has a UUID (a unique ID), an invoice counter that goes up by one each time, and a QR code.
Step 4: Build a Phase 1 QR code
The QR code content uses a format called TLV: Tag, Length, Value. Think of boxes in a row: each has a number (tag), a size (length) and content (value). The five Phase 1 tags are: seller name, VAT number, time stamp, total with VAT, and VAT amount. The result is encoded as Base64 text.
import base64
def tlv(tag, value):
data = value.encode("utf-8") # length is in bytes, not letters
return bytes([tag, len(data)]) + data
def phase1_qr(seller, vat_number, timestamp, total, vat):
payload = (
tlv(1, seller)
+ tlv(2, vat_number)
+ tlv(3, timestamp)
+ tlv(4, total)
+ tlv(5, vat)
)
return base64.b64encode(payload).decode("ascii")
# Example values only
print(phase1_qr("Example Trading Co", "300000000000003",
"2026-06-07T12:30:00Z", "115.00", "15.00"))
Put the printed text into any QR code library. Arabic names also work, because length is counted in UTF-8 bytes. In Phase 2, the QR code gets more tags, such as the invoice hash and the digital signature.
Step 5: Understand the stamp and the hash chain
Phase 2 adds two security ideas.
- Cryptographic stamp: your system signs each invoice with a private key. Like a wax seal, it proves the invoice is yours and unchanged.
- Hash chain: a hash is a short “fingerprint” of a file (ZATCA uses SHA-256). Each new invoice includes the hash of the previous invoice. If someone deletes or changes an old invoice, the chain breaks and it shows.
For the exact hashing and signing steps, follow ZATCA’s technical documents and sample files.
Step 6: Onboarding with a CSID
Your system must be registered first. The certificate ZATCA gives you is called a CSID (Cryptographic Stamp Identifier). The flow is:
- Create a private key and a certificate request (CSR) with your company details.
- Get a one-time password (OTP) from the Fatoora portal.
- Ask for a compliance CSID and send test invoices. ZATCA checks them.
- When the tests pass, ask for a production CSID and start live work.
Practice first in the sandbox on the developer portal. Nothing you send there counts as a real invoice.
Common problems
- The QR code does not scan in the official app. Check that each length is in bytes, and that the amounts have two decimals.
- ZATCA rejects the invoice hash. The XML was changed after hashing, even by one space. Hash the final file.
- The counter or chain breaks after a crash. Save the counter and last hash in your database, in the same step as the invoice.
Wrap-up
Now the strange words have meaning: two phases, two invoice types, UBL XML, a QR code, a stamp, a chain, and a CSID. If the official site says something different from this post, trust the official site.
Next idea: download the official sample invoices and SDK from the developer portal, and test your XML against them.