Someone has handed you a ticket that reads "make our system ZATCA compliant". It might be an ERP, a point of sale, a hospital information system, a school fee platform, a booking engine, or an in-house app nobody outside the building has ever seen. The ticket is one line. The work is not. This is an honest account of what a direct integration with the Fatoora platform contains, so you can decide whether to build it, host it, or buy it.
It assumes you know that a standard (B2B) invoice must be cleared before the buyer sees it and a simplified (B2C) one reported within 24 hours; if not, start with what Phase 2 requires.
What a direct integration contains
Roughly in the order you will meet it:
- Key pair and CSR. An ECDSA key on the secp256k1 curve, and a certificate signing request carrying your VAT number, establishment name, address and a set of ZATCA-specific extension attributes. Get one wrong and the request is rejected without much explanation.
- Compliance CSID. The merchant generates a one-time password in the Fatoora portal; you exchange it with the CSR for a compliance certificate. The OTP is single-use and short-lived, so onboarding has to be completable in one sitting.
- The compliance documents. If you issue both invoice types that is six: standard and simplified, with a credit and debit note for each. They must be produced correctly by your own code, not by hand, before you may issue anything real.
- Production CSID. Issued on passing that whole set. This is the certificate that signs real invoices, and it expires.
- UBL 2.1 XML. Not a template you fill in — a fixed schema with business rules on top, where element order is mandatory and tax categories, exemption reasons and note types come from controlled code lists.
- XAdES signing. The document is canonicalised, specific elements are excluded from the digest, a signed properties block carries the certificate digest and issuer details, and the result sits in exactly the position the specification requires. Byte-level correctness matters here: a whitespace difference after canonicalisation is a failed signature.
- The chain. Every device keeps an invoice counter value (
ICV) that only increments and the previous invoice hash (PIH): persistent, per-device, concurrency-sensitive state. Two workers issuing at once without a lock will claim the same position in the chain, and the break is permanent. - Two transmission paths. Clearance is synchronous and blocks the document: no cleared copy, nothing to send the buyer. Reporting happens after the sale, within 24 hours, so the customer already has their invoice — but a rejected simplified invoice still has to be corrected and resubmitted, which makes the response something you must read rather than discard. Different endpoints, different response shapes, different failure semantics, and reporting needs a retry queue that survives a restart.
- Validation responses. Rejections and warnings arrive as coded results — our own catalogue runs to 135 — and you have to map them to something users can act on, because "BR-KSA-44" means nothing to a clinic administrator. See the error catalogue, or BR-KSA-09 for a typical example.
- Renewal, forever. Certificates expire. Every device needs a fresh OTP and a renewal run, on a schedule someone owns after you have moved on to other work.
None of this is beyond a competent team. A first working integration is measured in weeks; the long tail — code lists, odd rejections, renewal tooling, the branch model — is measured in quarters.
The three realistic routes
| Build it yourself | Self-host open source | Send JSON to an API | |
|---|---|---|---|
| Certificates and CSR | You write it | Library handles it, you operate it | Handled for you |
| XML and signing | You write it | Library handles it | Handled for you |
| ICV / PIH state | Your database, your locking | Your database, your locking | Held per device by the service |
| Rejection codes | You map all of them | Raw codes, mostly unmapped | Shown with the official code and a link to its guide |
| Certificate renewal | You build and staff it | You build and staff it | Still needs the merchant's OTP |
| Invoice data leaves your servers | Only to ZATCA | Only to ZATCA | Yes, to the service |
| Best when | Compliance is your product, or policy forbids third parties | You want to own the component and can maintain it | Invoicing is a feature, not your product |
When building it yourself is the right call
Genuinely, sometimes it is, and any vendor who tells you otherwise is selling.
- Data residency or policy. If a government client, a hospital's information security policy or your own contracts forbid invoice data leaving your infrastructure, that decides it. No comparison table beats a policy.
- Compliance is the product. If you are building an e-invoicing product, the cryptography is not overhead — it is the thing you are selling.
- You have the team and want the component. Some organisations would rather own every dependency in the signing path and have the staff to maintain it for years. That is a legitimate choice, not a mistake.
Open-source implementations are a reasonable middle path: you still operate the state, the renewals and the error mapping, but you do not write the XAdES layer from scratch.
The alternative: post JSON, receive a signed invoice
The other route is to keep your system as the source of truth and treat compliance as a call. You post the invoice as JSON — seller, buyer, lines, totals, document type — and you get back the UBL 2.1 XML with its stamp and QR code — for a standard invoice, the cleared document as ZATCA returned it — plus a cleared or reported status, with the counter and hash chain maintained per device. Rejections come back with their official ZATCA code and a link to its guide. That is what our API reference documents.
If you are a software company with many merchants rather than one business, the shape is different and matters more. One partner key, a merchant onboarding link that lets each merchant enter their own Fatoora OTP without your support desk touching it, and branches and devices per merchant underneath. The Partner API reference and the partner guide cover that model. If your customers run stores, there are ready plugins for WooCommerce and Shopify, and an n8n node.
What we are is a compliance layer: signing, clearance and reporting, quotations, receipt vouchers, reports, and six years of downloadable signed XML so a merchant can leave with their archive. What we are not is an accounting system — no general ledger, no journal entries, no inventory, no payroll. If the ticket says "we need accounting", this is not that.
It is free during the launch period — an account starts with 50 invoices and expands when needed — which is enough to run a real integration test against your own data before anyone signs anything: create an account, or talk to us if you are evaluating for a portfolio of clients. More technical write-ups appear on the English blog.