Skip to content
Business Growth

ZATCA e-invoicing QR codes explained: fields, TLV and Base64

Selling in Saudi Arabia? Here is what the ZATCA invoice QR code contains, how the TLV and Base64 encoding works, and how to generate one correctly.

DDEVNTOM TeamEngineering and strategy2 min read
On this page
  1. What the QR code is for
  2. The five fields in the basic QR code
  3. What TLV means
  4. A working example
  5. Common mistakes
  6. Skip the manual work

If you issue invoices in Saudi Arabia, you will meet the ZATCA (Zakat, Tax and Customs Authority) e-invoicing rules. One visible part of them is the QR code printed on the invoice. It looks like a normal QR code, but what is inside it is very specific, and a small mistake in the encoding makes it fail validation.

What the QR code is for

The QR code lets a customer, an inspector or a scanning app read the key facts of an invoice instantly and check that they match the printed document. It was required on simplified tax invoices, the invoices a business issues to consumers, from the first phase of the e-invoicing rollout (the “generation” phase), and the later “integration” phase adds further requirements.

The five fields in the basic QR code

  1. 1Seller name, exactly as registered.
  2. 2VAT registration number of the seller.
  3. 3Invoice date and time, in ISO 8601 format, for example 2026-10-15T09:30:00Z.
  4. 4Invoice total including VAT.
  5. 5VAT amount on the invoice.

The later phase adds more data, such as a hash of the invoice and a cryptographic stamp, so check the current technical specification if your business has reached that phase.

What TLV means

TLV stands for Tag, Length, Value. Each field is stored as three parts, one after the other:

  • Tag: one byte that says which field this is (1 for seller name, 2 for VAT number, and so on up to 5).
  • Length: one byte that says how many bytes the value takes.
  • Value: the field itself, written as UTF-8 bytes.

You join the five TLV blocks into one sequence of bytes. Then you turn that whole sequence into Base64 text. That Base64 string is what you put into the QR code.

A working example

ts
function tlv(tag: number, value: string) {
  const bytes = new TextEncoder().encode(value); // UTF-8, so Arabic names work
  return Uint8Array.from([tag, bytes.length, ...bytes]);
}

export function zatcaBase64(f: { seller: string; vat: string; time: string; total: string; vatAmount: string }) {
  const parts = [tlv(1, f.seller), tlv(2, f.vat), tlv(3, f.time), tlv(4, f.total), tlv(5, f.vatAmount)];
  const all = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
  let offset = 0;
  for (const p of parts) { all.set(p, offset); offset += p.length; }
  return btoa(String.fromCharCode(...all)); // put this string in the QR code
}

Notice that the length is measured in bytes, not characters. An Arabic seller name uses more bytes than letters, which is the most common cause of a wrong length byte.

Common mistakes

  • Counting characters instead of bytes for the length.
  • Leaving the VAT amount out, or using the total before VAT.
  • A timestamp in the wrong format.
  • Putting the TLV text in the QR code instead of the Base64 version of it.
  • A QR code printed too small to scan reliably.

Skip the manual work

You can build this into your own invoicing system, or use our ZATCA QR code generator to create and download a correctly encoded code from your invoice details. If you need e-invoicing built into your software, talk to our team.

ZATCASaudi ArabiaE-invoicingQR code

Share

D

DEVNTOM Team

We design and engineer websites, software and automation for businesses in Pakistan, Saudi Arabia, the UK and beyond.

All articles

Ready to build yours?

Tell us about your project. We reply with a plan, a timeline and a quote.

Start a project