ZATCA e-invoicing compliance service
A standalone service that lets a hotel issue tax invoices that meet Saudi Arabia's e-invoicing rules: in the required format, digitally signed, QR-coded and sent to the tax authority.
2
invoice types
Business (B2B) and consumer (B2C)
2
languages on every invoice
Arabic and English
11
API endpoints
Onboarding, compliance and invoicing
The problem
In Saudi Arabia, businesses must issue electronic tax invoices through the Zakat, Tax and Customs Authority (ZATCA). An invoice has to follow a set XML format, carry a digital signature and a QR code, and be sent to ZATCA.
Business invoices must be cleared by ZATCA before they are issued; consumer invoices are reported. A hotel management system cannot invoice its guests legally without this.
What I did
- Built the service on my own, as a standalone NestJS backend with its own Next.js admin front end.
- Device onboarding: generating the certificate request and obtaining the certificate (CSID) from ZATCA.
- Invoice XML in the UBL 2.1 standard, cryptographic signing and the QR code.
- Clearance calls for business invoices and reporting calls for consumer invoices, with every ZATCA response stored.
- Credit and debit notes.
- Invoices stored in a hash chain, so each invoice is linked to the one before it.
- Invoice PDFs in Arabic and English.
- Support for several properties, each with its own invoicing device.
How an invoice is issued
Each step is in plain words. Open “Technical detail” for the engineering version.
Register the device
Register the property's invoicing device with ZATCA and receive its certificate.
Technical detail
Certificate signing request, compliance CSID and compliance checks, then the certificate used for signing. Re-onboarding is restricted.
Build the invoice
Turn the hotel bill into ZATCA's XML format.
Technical detail
UBL 2.1 XML for standard (B2B) and simplified (B2C) invoices, plus credit and debit notes.
Sign and stamp
Sign the invoice digitally and add the QR code a customer can scan.
Technical detail
Cryptographic signing, with the invoice hash chained to the previous invoice.
Send to ZATCA
Business invoices are cleared by ZATCA before they are issued; consumer invoices are reported.
Technical detail
Separate clearance and reporting API calls; the status and full response are stored for every invoice.
Issue the PDF
Produce the final invoice PDF in Arabic and English.
Technical detail
Handlebars templates rendered to PDF.
Results
- A working ZATCA e-invoicing service for the hotel PMS, covering business and consumer invoices, credit notes and debit notes.
- Invoices in Arabic and English, each linked to the one before it.
- Tax rules kept in one place: the PMS only calls the service.
Timeline
- Dec 2025Core invoicing engine, signing and compliance checks, QR code, clearance and reporting, B2B and B2C invoices, Arabic and English, and the admin front end.
What I learned
Compliance work rewards exactness: one wrong field and the invoice is rejected, so every invoice is validated before it is sent.
Keeping tax logic in its own service means a rule change touches one place, not the whole PMS.
Words on this page
- ZATCA
- Saudi Arabia's Zakat, Tax and Customs Authority.
- E-invoice
- A tax invoice created and shared electronically in a format the tax authority can check.
- UBL 2.1
- An international standard XML format for business documents such as invoices.
- CSID
- The certificate ZATCA issues to an invoicing device, used to sign its invoices.
- Clearance
- ZATCA checking and approving a business invoice before it is issued.
- Reporting
- Sending a consumer invoice to ZATCA after it is issued.
- Hash chain
- Linking each invoice to the previous one, so none can be removed unnoticed.