ZATCA Phase 2 integration: requirements, sandbox and a step-by-step guide
This guide takes you from the first ZATCA sandbox call to the first legal invoice in production, following the exact flow Matheel's engine runs against ZATCA's gateways.
What is ZATCA Phase 2?
ZATCA Phase 2 (the Integration Phase) requires a Saudi taxpayer's e-invoicing system to send every e-invoice to ZATCA's Fatoora platform. Standard (B2B) tax invoices are cleared by ZATCA before they reach the buyer; simplified (B2C) invoices are reported within 24 hours. Phase 1 (Generation) started on 4 December 2021; Phase 2 started on 1 January 2023 and applies to taxpayers in waves that ZATCA announces. Check whether your business is in wave 25.
ZATCA Phase 2 requirements
- Invoices as XML following UBL 2.1.
- An XAdES electronic signature with a CSID (cryptographic stamp identifier) issued per device (EGS) in the taxpayer's name.
- A UUID, a gap-free invoice counter (ICV) and the previous invoice's hash (PIH), which chain invoices together.
- A QR code carrying the invoice data and the signature.
- Every invoice sent to ZATCA through its API: clearance for standard, reporting for simplified.
Matheel does all of this in one request. The steps below are the path from zero to production.
-
Know the three environments
ZATCA runs three gateways, each with its own CSID; a certificate from one does not work in another:
- Developer portal (
sandbox): for testing. It accepts a published test OTP. Free on Matheel. - Simulation (
simulation): the taxpayer's real details and an OTP from their account, no legal effect. - Production (
production): legal invoices; needs a device plan.
- Developer portal (
-
File a ZATCA sandbox invoice without a CSID
See a real answer before onboarding anything: send
"environment": "sandbox"with nosellerand nocsid, and Matheel files it on the developer portal as a shared test taxpayer. Or press the button on the home page.curl https://matheel.app/api/v1/invoices -H "Authorization: Bearer $MATHEEL_KEY" \ -H "Content-Type: application/json" \ -d '{"environment":"sandbox","invoice":{"type":"simplified","counter":1,"uuid":"…","number":"INV-1", "line_items":[{"name":"Coffee","quantity":2,"price":"50.00"}], "subtotal":"100.00","vat_amount":"15.00","total":"115.00"}}' -
Prepare the taxpayer's details
Most onboarding rejections come from seller data. Get it right once:
- VAT number: 15 digits, starting and ending with 3.
- Legal name in Arabic and the commercial registration number (CRN).
- The full national address: street, building number (4 digits), district, city, postal code (5 digits). A missing address gives BR-KSA-09 and BR-KSA-37.
-
Get an OTP from the Fatoora portal
For simulation and production, the taxpayer logs in to fatoora.zatca.gov.sa, chooses to onboard a new solution unit, and copies the OTP. It expires quickly, so use it right away. The developer portal needs no OTP: Matheel uses the test value.
The CSID is issued in the taxpayer's own name, so you cannot get one for a customer without the OTP from their account. Plan this step with your first customer early.
-
Onboard the device (CSID) in one call
By hand it is four steps: a CSR with a secp256k1 key, a compliance CSID in exchange for the OTP, six compliance checks (invoice, credit note and debit note, standard and simplified), then the production CSID. On Matheel it is one request:
curl https://matheel.app/api/v1/onboarding -H "Authorization: Bearer $MATHEEL_KEY" \ -H "Content-Type: application/json" \ -d '{"environment":"simulation","otp":"123456","seller":{"vat_number":"3…3","legal_name_ar":"…","crn":"…", "address":{"street":"…","building_number":"1234","district":"…","city":"Riyadh","postal_code":"12211"}}}'You get back a
csidwithprivate_key,binary_security_tokenandsecret. Store them safely: Matheel keeps none of it, and you send them with every invoice. -
Keep the invoice chain per device
Each device (EGS) has a chain: a counter (ICV) that goes up by one per invoice, and the hash of the last accepted invoice (PIH) sent with the next one.
- Advance the counter and store the hash only after a 200.
- A rejected invoice does not consume a number: fix it and resend with the same counter, or you get BR-KSA-16.
- Resending the same
uuidis billed once.
-
File invoices and archive the XML
Each invoice goes to
POST /api/v1/invoiceswithsellerandcsid. The response carries:invoice_xml: the stamped legal document. Archive it.qr: print it on the invoice.invoice_hash: for the next invoice.warningsanderrors: ZATCA's own text. A warning does not block acceptance; an error returns 422.
Clearance vs reporting
Standard (B2B, standard): needs the buyer's VAT number and address, and is cleared: ZATCA stamps it before it reaches the buyer. Simplified (B2C, simplified): needs no buyer details and is reported within 24 hours of issue. Matheel picks the path from type.
Start at step two, right now
A real invoice on ZATCA's developer portal, no certificate and no key.
Try an invoice or read the API reference →