Implementing ZATCA E-Invoicing in Node.js
A practical walkthrough of Saudi ZATCA-compliant QR e-invoicing and automated PDF invoices in a Node.js commerce backend.
If you're shipping commerce in Saudi Arabia, ZATCA e-invoicing isn't optional — every invoice needs a TLV-encoded QR code carrying the seller name, VAT number, timestamp, total, and VAT amount. Here's how I wired it into a Node.js backend.
TLV, then Base64
ZATCA's Phase 1 QR is a Tag-Length-Value byte string, Base64-encoded. Each field is a tag byte, a length byte, then the UTF-8 value:
function tlv(tag, value) {
const buf = Buffer.from(value, "utf8");
return Buffer.concat([Buffer.from([tag, buf.length]), buf]);
}
const qrPayload = Buffer.concat([
tlv(1, sellerName),
tlv(2, vatNumber),
tlv(3, isoTimestamp),
tlv(4, total),
tlv(5, vatAmount),
]).toString("base64");Render the invoice deterministically
Generate the PDF server-side from an HTML template so every invoice is reproducible and archivable. Embed the QR as a data URL and you have a self-contained, compliant document.
Make compliance a service
Don't scatter ZATCA logic across controllers. Put encoding, rendering and storage behind one
InvoicingService — when Phase 2 cryptographic signing lands, you change one module, not ten.
Compliance work is rarely glamorous, but getting the primitives right once means you never think about it again.