Qoyod
Pricing

Knowledge Base

QR Code Structure on the E-Invoice: TLV, Base64 & the 9 Tags

Every Saudi electronic invoice carries a small QR code in its corner. A casual reader sees only a black square, but behind it lies a tightly defined data structure mandated by the Zakat, Tax and Customs Authority (ZATCA), specified precisely byte by byte. This guide is aimed at developers and technical accountants: we break down the code’s internal structure, the order of its nine tags, how the string is built from TLV encoding then Base64, where it is injected inside the XML file, and how to read and validate it programmatically.

If what you are looking for is the general idea (what the code is, why the Authority mandated it, and how to read it from your phone), then the right place is our conceptual guide: The QR code on the electronic invoice. Here, however, we deal with the technical layer: the bytes, the ordering, and the build and decode steps.

Qoyod’s electronic invoicing software issues this code in conformance with the Authority’s specification on every invoice automatically, so you do not need to build or encode it by hand. But understanding its structure helps you validate invoices, read the code’s content during audits, and diagnose any error that arises when integrating with the Fatoora platform.

What does the QR code on the electronic invoice carry?

The code is not a link, nor an embedded image, but an encoded text string that condenses the invoice’s identity. This string is made up of a set of ordered fields, each field written in TLV format. TLV is an abbreviation of three elements:

  • Tag: a number that identifies the field type. For example, tag 1 means the seller name, and tag 2 means the VAT number.
  • Length: the number of bytes the field’s content occupies.
  • Value: the actual content of the field as bytes.

The fields are written one after another in this triple order, then the resulting string is combined and converted into a Base64 text. This text is what is stored inside the QR code and appears on the invoice.

The benefit of this design is that the code carries the invoice data compressed and machine-readable at the same time. The reader needs no internet connection and no reference to a database; everything required is contained inside the code. This property serves the field auditor inspecting an invoice in a store, as well as the buyer who wants to verify the details of their receipt with a quick scan. The tight structure is what makes this possible in a small code that any phone can read in less than a second.

For a deeper look at the mechanics of TLV (Tag-Length-Value) encoding on its own, and at the Base64 conversion and how it works, we dedicate a separate technical guide to each. Here we focus on the complete structure of the invoice code and how these pieces come together.

The nine tags in the invoice code, in order

The Phase Two specification defines nine tags for the QR code on the simplified tax invoice (B2C). The order is binding: it starts with tag 1 and ends with tag 9, and their order may not be swapped. The following table presents them as required by the Authority:

The nine QR code tags in order
The fields the QR code carries in Phase One and Phase Two.
The nine tags

1–5: seller name, VAT number, timestamp, total, VAT (Phase One)

6: invoice hash (SHA-256)

7: cryptographic stamp (ECDSA signature)

8: public key

9: Authority stamp

Tags 6–9 are Phase Two additions related to validation.
Tag Field Value type Phase
1 Seller name UTF-8 text One and Two
2 Seller’s VAT registration number Numeric text (15 digits) One and Two
3 Invoice timestamp Text in ISO 8601 format One and Two
4 Invoice total with VAT Decimal number One and Two
5 VAT total Decimal number One and Two
6 Hash of XML invoice Base64 text of a SHA-256 hash Two
7 Seller’s cryptographic stamp (ECDSA signature) Digital signature bytes Two
8 Seller’s public key Public key bytes Two
9 Authority stamp on the public key (ZATCA stamp signature) Authority signature bytes Two

Tags 1 through 5 are the core data that has been present since Phase One of electronic invoicing. Tags 6 through 9, however, are the cryptographic layer added in Phase Two (Integration), and they give the code the ability to independently verify the invoice’s authenticity without referring back to any server.

A precise note on tag 9: it is the Authority’s signature on the seller’s public key, not a signature on the invoice itself. Its function is to prove that the public key in tag 8 was genuinely issued by an approved device that registered its certificate (CSID) with the Fatoora platform. In this way the code ties the invoice to an identity authenticated by the Authority.

How is the TLV string built for each tag?

Each tag is written as three consecutive chunks of bytes: one byte for the tag, one byte for the length, then the value bytes. Take tag 1 (seller name) as an example. Suppose the seller name is Qoyod Company (nine bytes in UTF-8 encoding, for illustration):

Tag    = 0x01            // tag number: 1
Length = 0x09            // value length: 9 bytes
Value  = D8 B4 D8 B1 ... // name bytes in UTF-8 encoding

So the complete TLV chunk for this tag is the byte sequence: 01 09 [value bytes]. The same logic applies to the rest of the tags. Example, tag 4 (invoice total) if its text value is 115.00 and its length is six bytes:

Tag    = 0x04
Length = 0x06
Value  = "115.00"   // six ASCII bytes

The length rule matters for the cryptographic fields. The cryptographic stamp in tag 7 and the key bytes in tag 8 are not readable text but raw bytes, so the length byte carries their actual count before writing them directly. Do not convert these bytes into text before building the TLV string, because the conversion will change their length and break validation.

Combining the string then converting it to Base64

After building the TLV chunk for each tag separately, the nine chunks are combined into a single byte string in order, from tag 1 to tag 9. This binary string is not suitable for direct storage in the text-based QR code, so it is converted into Base64 text.

// build steps in order
1. Build the TLV chunk for each tag:  tag + length + value
2. Combine the nine chunks in order:  tlv1 + tlv2 + ... + tlv9
3. Convert the resulting byte string into Base64
4. Place the Base64 text inside the QR code

Base64 encoding converts the binary bytes into safe text that can be stored in the code. The resulting text is what any QR reader reads, and it is the very same text that is injected inside the invoice’s XML file. For a deeper look at how the Base64 conversion works and why it was chosen specifically, we dedicate a separate technical guide to it.

How the QR code is built
From invoice fields to a print-ready QR code.
1

Invoice fields

2

TLV segments for each field

3

Combined byte string

4

Base64 encoding ← QR code

The fields are encoded with TLV, then combined and converted to Base64 inside the code.
Start today

Authority-compliant QR codes on every invoice, automatically

Qoyod builds the TLV string, converts it to Base64, and injects it into the QR code on every invoice, in conformance with the Phase Two specification, without any manual step from you.

Start your free trial and issue compliant invoices

A complete step-by-step example of building the string

Let’s build a complete TLV string for a simple simplified invoice from the five core data fields, then show how the cryptographic fields join it. Assume these values:

  • Seller name: Qoyod Company
  • VAT number: 300000000000003
  • Timestamp: 2026-06-24T11:45:30Z
  • Invoice total with VAT: 115.00
  • VAT total: 15.00

We build the TLV chunk for each value separately. Text values are first converted into UTF-8 bytes, then we place the tag number and the byte length in front of them:

// Tag 2: VAT number (15 digits = 15 ASCII bytes)
Tag    = 0x02
Length = 0x0F           // 15 in hexadecimal
Value  = "300000000000003"

// Tag 3: timestamp (20 ASCII bytes)
Tag    = 0x03
Length = 0x14           // 20
Value  = "2026-06-24T11:45:30Z"

// Tag 5: VAT total (5 bytes)
Tag    = 0x05
Length = 0x05
Value  = "15.00"

Notice the length byte for tag 2: its value is 0x0F which is 15 in decimal, because the Saudi VAT number is 15 digits. And notice tag 3: its length is 20 bytes because the full ISO 8601 format with the letter Z at its end occupies 20 ASCII characters, and each ASCII character is one byte. Here the number of characters matches the number of bytes because the characters are Latin. Tag 1 (the Arabic seller name), however, is different, since a single Arabic character occupies two bytes in UTF-8, so the length in Qoyod Company bytes is larger than its character count.

After building the five chunks, we combine them in order into a single byte string. The string begins with the bytes of tag 1 (tag number, then length, then the Arabic name bytes), followed immediately by the bytes of tag 2, and so on up to tag 5 in Phase One, or up to tag 9 in Phase Two.

The four cryptographic fields in detail

Tags 6 through 9 are what turn the code from a visual summary into a verifiable document. We detail each of them:

Tag 6: invoice hash

Before computing the hash, the XML file is passed through a Canonicalization process that ensures a uniform format for whitespace and ordering. Then a SHA-256 hash of the result is computed and stored encoded in Base64. Any later modification to the invoice, even by a single character, changes this hash entirely, so the comparison fails during validation. This is what makes the hash the basis for tamper detection.

Tag 7: the cryptographic stamp (signature)

The stamp is a digital signature computed over the invoice hash using the seller’s private key stored in their certificate. The signature algorithm is the Elliptic Curve Digital Signature Algorithm (ECDSA). The output is raw bytes written as-is in the TLV string without conversion to text. Its function is to prove that the holder of the private key is the one who issued this specific invoice.

Tag 8: the public key

The public key is the public counterpart of the private key. The code carries it so any reader can verify the stamp in tag 7 without possessing the private key. This design enables validation without connecting to any server: everything required is contained inside the code itself.

Tag 9: the Authority stamp (ZATCA stamp)

The last tag is the Authority’s signature on the seller’s public key. This signature ties the key to the device certificate (CSID) that the seller registered with the Fatoora platform. Without it, any party could generate a key pair and sign a forged invoice. The presence of the Authority stamp proves that the public key in tag 8 was genuinely issued by an approved device that passed the registration and compliance procedures.

The device certificate (CSID) and its relation to the code

The cryptographic tags do not work without an approved certificate. To enter Phase Two, the seller’s system generates a Certificate Signing Request (CSR) and sends it to the Fatoora platform along with a one-time password. The platform returns a Compliance CSID to test the system in the sandbox environment. After passing the compliance checks, the seller receives the Production CSID, which is the one that signs every live invoice.

Each device or branch needs its own certificate. The public key in tag 8 is the public part of this certificate, and the Authority stamp in tag 9 is its signature on that key. This is how trust chains: from the Authority, to the device certificate, to the invoice. Qoyod manages this certificate lifecycle on your behalf and uses it to sign every invoice automatically.

Details of the timestamp and its format

Tag 3 is one of the most common sources of warnings during manual building. The specification requires the full ISO 8601 format with both date and time. The correct format is of the form YYYY-MM-DDThh:mm:ssZ, such as 2026-06-24T11:45:30Z. The letter T separates the date from the time, and the letter Z indicates Coordinated Universal Time.

The recurring errors here: using a local format instead of the standard format, or omitting the letter T, or leaving out the seconds field. Any deviation from the format causes the Authority’s tools to log a warning, even if the rest of the code is correct. Therefore the timestamp is generated automatically in approved solutions, always in the required format.

Where is the QR code injected inside the XML file?

The Phase Two electronic invoice is generated in a standard XML format (UBL 2.1). The Base64 text that represents the QR code is not written as an image, but is stored in a dedicated field inside the structure under an additional reference at the document level.

<cac:AdditionalDocumentReference>
  <cbc:ID>QR</cbc:ID>
  <cac:Attachment>
    <cbc:EmbeddedDocumentBinaryObject mimeCode="text/plain">
      [QR code Base64 text here]
    </cbc:EmbeddedDocumentBinaryObject>
  </cac:Attachment>
</cac:AdditionalDocumentReference>

The identifier QR is what distinguishes this field from the other document references in the invoice. The receiving endpoint system (or rendering tool) reads the Base64 text from this field and draws it as a visible QR code on the display copy, such as the PDF/A-3 file that embeds the XML inside it.

Qoyod generates the complete XML file in UBL 2.1 format with the QR code in its correct place automatically, so you never deal manually with XML tags or with the field’s position.

How do you read the QR code and decode its structure?

To validate an invoice, you need to reverse the build steps. You start by reading the Base64 text from the code, then decoding it into the original byte string, then splitting the string into the nine tags. The decode steps in order:

// code decode steps
1. Read the Base64 text from the QR code
2. Decode the Base64 into a byte string
3. Read the first byte = the tag number (Tag)
4. Read the next byte = the value length (Length)
5. Read (Length) number of bytes = the value (Value)
6. Repeat from step 3 until the end of the string

Each of these cycles extracts a single tag. After decoding the nine tags you obtain the full invoice data: the seller name, their VAT number, the timestamp, the totals, the hash, and the cryptographic stamp.

Reading alone is not enough for validation. To confirm the invoice has not been modified, you compute the SHA-256 hash of the XML file and compare it against the value of tag 6. Then you verify the cryptographic stamp in tag 7 using the public key in tag 8, and verify that the key itself is signed by the Authority via tag 9. The success of these three checks means the invoice is authentic, unmodified, and issued by an approved device.

How the QR code is validated
Three layers to validate the invoice via the code.
1

Match the invoice hash (tag 6)

2

Verify the signature with the public key (7 and 8)

3

Verify the Authority stamp (tag 9)

Passing the three layers confirms the invoice’s authenticity and integrity.

The difference between Phase One and Phase Two in the code’s structure

In Phase One (Generation), the QR code carried only the first five tags: seller name, VAT number, timestamp, invoice total, and VAT total. There was no cryptographic layer, so the code was a means of displaying invoice data rather than a validation tool.

Phase Two (Integration) added the last four tags: the hash, the cryptographic stamp, the public key, and the Authority stamp. This addition turned the code from a visual summary into an independently verifiable document. Any reader who holds the public key can confirm the invoice’s authenticity without connecting to any server.

The detail of the Clearance mechanism for business-to-business (B2B) tax invoices versus Reporting for simplified (B2C) invoices is beyond the scope of this technical guide to the code. What matters here is that the QR code on the simplified invoice carries all nine tags in Phase Two.

Practical validation of the code, step by step

After decoding the nine tags, validation is carried out across three sequential layers, and the code is only accepted if all of them succeed. The first layer is hash matching. You take the invoice’s XML file, pass it through the same canonicalization process the issuer used, then compute the SHA-256 hash of the result. You compare the resulting value against the value of tag 6. If they match, the invoice has not changed since it was signed. Any difference means the content was modified.

The second layer is verifying the stamp. You extract the public key from tag 8 and use it to verify the signature in tag 7 against the invoice hash. A successful verification proves that the holder of the corresponding private key is the one who signed the invoice, not another party.

The third layer is verifying the Authority stamp. You confirm that the public key in tag 8 is genuinely signed by the Authority via tag 9. This layer prevents forgery: it is not enough for a party to sign an invoice with their private key; their public key must also be stamped by the Authority, meaning it was issued by an approved device.

The Authority’s official validation tool is available for inspecting codes, and an internal tool can also be built to perform the steps above. The benefit of understanding the structure is that you can diagnose the cause of a failure precisely: is the problem in the hash (modified content), in the stamp (a non-matching key), or in the Authority stamp (a non-approved certificate)? Each layer points to a different cause.

Do all invoice types carry the same code?

The QR code is mandatory on the simplified tax invoice directed at the end consumer (B2C), and it carries the nine tags in Phase Two. These invoices are delivered to the buyer immediately then reported to the Authority within twenty-four hours.

The standard business-to-business (B2B) tax invoice follows a different path: it is sent to the Authority first for clearance, and the Authority returns it signed with its stamp before it is delivered to the buyer. The detail of this path and what it adds to the structure is beyond the scope of this guide, which focuses on the simplified invoice code. What matters is that the mechanism of building the TLV string and converting it to Base64 is the same in both cases; the difference is in the processing path, not in the code’s composition.

Why was the TLV format chosen specifically?

The TLV format is not a random choice. It is a compact format that needs no separators between fields, because the length byte determines where each field ends and the next begins. This keeps the string short enough to be stored in a medium-density QR code that an ordinary phone reads quickly. Alternative text formats (such as JSON) would inflate the string’s size and increase the code’s density, making it harder to scan.

TLV also handles raw binary data (the signature and key bytes) smoothly, whereas text formats require additional encoding for each binary field. This balance between compactness and support for binary data is why the Authority adopted it in the code specification.

Common errors when building the code manually

If you are building the code in a custom system instead of relying on an approved solution, these are the most recurring errors:

  • Swapping the order of the tags: the order is binding from 1 to 9. Any swap makes the Authority’s tools reject the code.
  • Computing the length by character count instead of bytes: in Arabic fields in UTF-8 encoding, a character occupies more than one byte, so the length is computed in bytes, not by character count.
  • Converting the signature bytes to text before building: tags 7, 8, and 9 are raw bytes, and converting them breaks validation.
  • Neglecting the final Base64 encoding: storing the raw byte string in the code instead of converting it to Base64 makes the text not readable correctly.
  • An error in the timestamp format: tag 3 requires the ISO 8601 format, and any other format causes a warning during validation.

These errors are the main reason behind invoice rejections or the appearance of warnings during integration with the Fatoora platform. The approved solution avoids every one of them because it builds the code according to the specification byte by byte.

There is a sixth, less obvious but common, error: the ordering of the fields inside the XML file before computing the hash. If you compute the hash on a non-canonicalized file, it will differ from the hash the Authority’s tool computes, and validation fails even though the invoice content is correct. So the canonicalization step before hashing is not a minor detail, but a fundamental condition for the success of tag 6 matching. Any system that builds the code manually must perform canonicalization in the same way the Authority adopts, character by character.

The practical takeaway for anyone building it themselves: adhere to the order from 1 to 9, compute lengths in bytes not characters, keep binary data raw, apply canonicalization before hashing, use the standard timestamp format, and convert the final string to Base64 before placing it in the code. Each one of these steps is a potential point of failure if neglected.

How does Qoyod help you with a compliant QR code?

You do not need to write a single line of code-building logic. Qoyod handles the complete cycle for every invoice:

  • It gathers the invoice’s nine fields and builds a TLV chunk for each tag with its correct length in bytes.
  • It computes the SHA-256 hash of the XML file and signs the invoice with the cryptographic stamp via the registered device certificate (CSID).
  • It combines the string, converts it to Base64, and injects it into the correct XML field within the UBL 2.1 structure.
  • It draws the QR code on the display copy, and embeds the XML inside a PDF/A-3 file for Phase Two customers.

The result is an invoice ready for clearance or reporting with a QR code compliant with the Authority’s specification, without any technical intervention from you.

Frequently asked questions

How many tags are in the QR code on the electronic invoice?
Nine tags in Phase Two: five core tags (seller name, VAT number, timestamp, invoice total, VAT total) and four cryptographic tags (the hash, the cryptographic stamp, the public key, the Authority stamp).

What is the difference between the code’s structure in Phase One and Phase Two?
Phase One carries only the first five tags. Phase Two adds tags 6 through 9 to enable independent cryptographic verification of the invoice’s authenticity.

Is the field length computed by character count or bytes?
By byte count. An Arabic character in UTF-8 encoding occupies more than one byte, so relying on character count breaks validation.

Where is the QR code’s Base64 text stored inside the XML file?
Inside an AdditionalDocumentReference element with the identifier QR, within the EmbeddedDocumentBinaryObject field in the UBL 2.1 structure.

What does the ninth tag represent?
The Authority’s stamp on the seller’s public key, which proves that the key was issued by an approved device that registered its certificate (CSID) with the Fatoora platform. It is not a signature on the invoice itself.

Do I need to build the QR code manually?
No. Qoyod builds the string, converts it to Base64, and injects it into the invoice automatically in conformance with the Phase Two specification, so no build code is needed from your side.

Guides

Continue your learning journey

Explore the rest of Qoyod’s guides, or start applying what you’ve learned.

Live webinars hosted by the Qoyod team to help you use the software easily and answer your questions.

Discover Qoyod’s latest updates, ongoing improvements, and new features in one place.

Our team is ready to help you and provide instant support for any issue you face, around the clock.