← Blog Guides 8 min read · 26 September 2026

ZATCA e-invoicing for POS and SaaS platforms: Phase 2 for every merchant you serve

One platform connected to many merchant establishments, each with its own certificate and its own chain of invoices per till
One integration on your side. One ZATCA identity, and one chain per till, on each merchant's.

The ticket reads "every merchant on our platform needs ZATCA Phase 2". It lands at POS vendors, ERP and accounting products, booking and clinic systems, and any SaaS whose customers sell in Saudi Arabia. The first design decision is seeing what it really asks for: not one integration, but one ZATCA identity per merchant, and one signing unit per till or branch inside it. Get that model right and the rest is engineering. Get it wrong — one certificate for everyone — and every invoice you sign is in the wrong name.

What Phase 2 means for each merchant

Each merchant is the issuer of its own invoices, under its own VAT number, whatever your platform does for it. Per merchant, that means:

  • Its own onboarding OTP. ZATCA issues the one-time password to the taxpayer inside the Fatoora portal. It never reaches you or any vendor: the merchant generates it and hands it over. Codes are single-use, valid for an hour, one per device.
  • Its own certificates. The compliance CSID and then the production CSID are issued for that merchant's identity. Name, VAT number and registration number are bound into the certificate, which is why they cannot change after connecting.
  • Its own chain per device. Every signing unit, ZATCA's EGS, keeps an invoice counter (ICV) and the previous invoice hash (PIH). A merchant with four tills has four chains, each onboarded with its own OTP.
  • Two transmission paths. A standard (B2B) invoice is cleared by ZATCA before the buyer receives it; a simplified (B2C) one is handed over at once and reported within 24 hours. The buyer decides which.

Your own subscription invoice to each merchant is a separate matter: your supply, under your VAT number, never mixed into theirs.

The build path

Building it means, per merchant and per device: a key pair and CSR, the compliance CSID, the compliance documents ZATCA validates before it releases the production CSID, UBL 2.1 generation, XAdES signing, ICV and PIH state under a lock, clearance and reporting with their different failure semantics, validation codes turned into messages a cashier can act on, and renewal of every certificate before it expires. What a direct integration really involves goes through each step. For a platform the multiplier is what hurts: onboarding, chain state and renewals are per device, across every merchant, for as long as they stay with you. The OTP and CSID lifecycle is where most support tickets start.

Building is the right call in three cases: your tills must sign on the device with no connection, a customer's policy forbids invoice data leaving your infrastructure, or compliance is the product you sell.

The partner path

ZATCA Tools, an independent e-invoicing platform, offers the other route as a Partner API. Everything below is from that reference and the partner guide.

  1. One partner key. A single ztkp_live_ key, kept server-side. There are no per-merchant keys.
  2. A merchant per request. POST /api/partner/merchants with the merchant's name, owner email and VAT number, and optionally its national short address, from which the full address is resolved. The registration number is required before connecting. The response carries the merchant's id and a ready onboarding URL.
  3. The merchant connects to Fatoora. Send them the signed onboarding link, valid 72 hours, or collect the OTP in your own screen, pass it to POST /api/partner/merchants/{id}/connect and poll /status; certificate and compliance checks typically settle in under two minutes. Missing details come back as merchant_incomplete before the single-use code is spent.
  4. Branches and devices. POST /api/v1/branches, then one device per till, each with its own OTP, certificate and chain. Every merchant and device reports its certificate_expires_at, and re-onboarding a device takes a fresh OTP from the merchant.
  5. Issue as the merchant. The same /api/v1 endpoints as the API reference, with your partner key and an X-Merchant-Id header.
POST /api/v1/invoices
Authorization: Bearer ztkp_live_...
X-Merchant-Id: 34
Idempotency-Key: order-18821

{ "type": "simplified", "branch_id": 3, "device_id": 7,
  "prices_include_vat": true,
  "lines": [ { "name": "Printer paper A4, box", "quantity": 2, "unit_price": 57.50 } ] }

The response carries status (accepted, warnings or rejected), the totals, the channel and counter, and links to the signed XML, the PDF and the QR code. A partner portal shows your operations team which merchants are connected, which are stuck at the OTP and which have rejections.

What stays your platform's job

A service can sign, chain and transmit. It cannot know what was sold. Four things stay with you:

  • The sale data. Lines, quantities and prices as your system holds them. If your prices include VAT, send them unchanged with prices_include_vat: dividing by 1.15 and rounding each line loses halalas the invoice cannot recover.
  • The tax category per line. S at 15%, or Z, E or O with a tax_reason_code from ZATCA's list, such as VATEX-SA-35 for medicines. One invoice may mix categories. A reason is never guessed for you: left out, the line takes the reason that merchant last used for that category, or goes without one and ZATCA accepts it with a warning.
  • The buyer's identity. A standard invoice needs the buyer's name and VAT number or, for a buyer without VAT registration, another identifier with its scheme, such as 700 for a government body or NAT for a Saudi national ID (BR-KSA-14). A line exempt as private education or healthcare needs the buyer's national ID even on a simplified invoice (BR-KSA-49).
  • The type. Standard or simplified is decided per sale, by the buyer, in your checkout.

Offline tills and the 24-hour window

A simplified invoice is handed to the customer at the sale and reported within 24 hours of issue. The window runs from issue, not from the moment a rejection arrives, so a rejected report is fixed the same day (reading the code).

Be clear about the architecture. Our API signs in the cloud and reports in the same call: the document, its QR code and its place in the device's chain exist only when the call returns, so a till must reach the API at the moment it issues. If your tills must print Phase 2 receipts through an outage, that takes signing on the device itself, which is the build path. What the API gives an intermittently connected till is safe retries. What a till prints while no invoice has been issued is a policy to agree with your merchants, not a receipt dressed up as one.

B2B clearance latency

A standard invoice is cleared inside the same call, so the response already holds ZATCA's verdict and, for a cleared document, the XML ZATCA stamped. The reference puts signing and clearance at one to three seconds and recommends a 30-second client timeout. Design the B2B checkout to wait for that answer: nothing goes to the buyer before clearance returns.

Idempotency and failure handling

Send your order id as external_id or an Idempotency-Key header. A repeat with the same key returns the document already issued — 200 instead of 201, same body — never a duplicate, even when repeats arrive together. Keys are unique within one merchant and namespaced per document kind, so a refund's credit note can reuse its order's id. Two rules follow: never reuse a key for a different sale, because the earlier document comes back; and if one merchant sells through two sources whose order numbers can repeat, prefix the key with the source.

ResponseMeaningWhat to do
422 and other 4xxRefused before signing; nothing numberedFix what the message names, resend
429Rate limit reachedWait for Retry-After, retry
5xx or timeoutOutcome unknown to youRetry with the same external_id
201, warningsAccepted, with notesIssued; fix the data upstream
201, rejectedRefused by ZATCA; keeps its number and chain positionCorrect the data, issue a new document
403 seat_releasedThe merchant's seat was releasedReinstate it; reads still work

Refunds and corrections are never edits: POST /api/v1/notes against the original, with the reason ZATCA requires (BR-KSA-17).

How partner seats work

Partner merchants issue without an invoice ceiling. Each merchant occupies one seat, reserved when you create it, with its paid term starting on the day the merchant connects to ZATCA and running monthly or yearly under your agreement. A merchant that never connects costs nothing, an expired term never stops a merchant issuing, and a merchant who leaves is released so the seat can serve someone else, with nothing deleted, since ZATCA requires a six-year archive. Your merchants never see an invoice from us; what you charge them is yours to decide. Seat numbers and terms are agreed per partner, so there is no price list.

If your platform serves Saudi merchants, tell us how many and on which system, and read the Partner API reference; the guide for software houses covers your own invoicing. If you are one business connecting its own system, the API is the route: one week free, no payment, from the day you connect, then very competitive plans: 49 SAR a month for Growth and 149 for Business, on a smooth, fast system that signs each invoice and sends it to ZATCA in seconds — start here.

Frequently asked questions

Can one ZATCA certificate cover every merchant on our POS or SaaS platform? +
No. ZATCA issues certificates per taxpayer, under that taxpayer's own VAT number, and per signing device it onboards. Every merchant is the issuer of its own invoices, so each needs its own onboarding with an OTP from the Fatoora portal, its own production CSID, and its own invoice counter (ICV) and previous-invoice-hash (PIH) chain for each till or branch device. A platform signing everyone's invoices with one certificate would be issuing them in the wrong name.
Who enters the Fatoora OTP when a platform onboards a merchant? +
The merchant generates it, and nobody else can: ZATCA issues the one-time password to the taxpayer inside the Fatoora portal, single-use and valid for one hour, one code per device. What the platform controls is where it is typed. With the ZATCA Tools Partner API, the merchant either types it into your own screen, which passes it to the connect call, or opens a signed onboarding link returned for each merchant and valid for 72 hours.
What happens when a POS till is offline at the moment of sale? +
With a cloud signing API, the invoice, its QR code and its place in the device's chain exist only once the call returns, so the till needs a connection at the moment it issues. Tills that must print Phase 2 receipts through an outage need signing on the device itself, which means building the integration. For intermittent connections, send each sale with its order id as external_id and retry until it succeeds: a repeat returns the invoice already issued and never creates a duplicate, and the simplified invoice is reported to ZATCA inside the same call.
How do we stop a retried API request from issuing a duplicate e-invoice? +
Send your own order id as external_id, or as an Idempotency-Key header. A repeated request with the same value returns the document already issued, with HTTP 200 instead of 201 and the same body, even when the repeats arrive at the same moment. Keys are unique within one merchant and namespaced per document kind, so a credit note may reuse its order's id. Never reuse a key for a different sale, and prefix keys with their source when one merchant sells through two systems whose order numbers can repeat.
How is the ZATCA Tools Partner API priced? +
Per merchant seat, with the number of seats and the terms agreed per partner, so there is no public price list. A seat is reserved when you create a merchant, and its paid term starts only on the day that merchant connects to ZATCA, running monthly or yearly under your agreement. A merchant that never connects costs nothing, an expired term never stops a merchant issuing, and a released seat can serve another merchant. Partners get in touch through the partners page.
Ready to connect your business?

Connecting is free and takes under five minutes.

Start for free