Qoyod
Pricing

Knowledge Base

The Cryptographic Stamp: XML Technical Specification (UBL 2.1)

This is a technical document that explains the exact position of the cryptographic stamp inside an electronic invoice file in UBL 2.1 format, and the elements it consists of at the XML tag level. Its goal is to help developers and integration engineers understand how the stamp is built, where it is inserted, and how the Fatoora platform validates it. If you are looking for a conceptual explanation of what the cryptographic stamp means and why it is mandated, it is best to start from the article The cryptographic stamp in the electronic invoice. Here we descend to the field and tag level.

The cryptographic stamp in Saudi electronic invoicing is not a single field you write in the invoice. It is a composite XML structure that lives inside a standard extension called ext:UBLExtensions, and it binds the invoice content to the issuer’s identity, to a timestamp, and to a chain of digest values. Understanding this structure at the tag level is the difference between an integration that succeeds on the first attempt and one that the authority’s system rejects with cryptic errors.

This document aligns with the requirements of the Zakat, Tax and Customs Authority (ZATCA) for phase two of electronic invoicing, and relies on the UBL 2.1 standard with a XAdES signature file. All snippets below are simplified for explanatory purposes and are not a substitute for the authority’s official documentation. For a broader introduction to the structure of electronic invoicing and its technical documents, see the section Learn electronic invoicing.

Who this document is for and what it covers

This document is aimed at three audiences: the developer building an integration with the Fatoora platform, the integration engineer auditing existing invoices and looking for the reason they were rejected, and the technical accountant who wants to understand what happens inside the file rather than on the system interface. The business owner who issues invoices from an approved system does not need this level of detail.

We focus here on four questions: where exactly the stamp is located inside the XML file, which elements it consists of at the tag level, how each value in it is computed, and how the Fatoora platform validates it. We do not repeat the explanation of what the stamp is or why it was mandated, as that is the subject of the conceptual article. Here the level is purely technical.

As a matter of terminology, we use “digest” for digest and hash, and “the cryptographic stamp” to refer to the signature value and the structure surrounding it. Wherever an English term such as SignedInfo or SignatureValue appears, it is an actual XML element name that must be written literally as is, not translated.

Where does the cryptographic stamp live inside the XML file?

The electronic invoice is an XML file built on the UBL 2.1 standard. The main body of the invoice contains the seller, buyer, line item, and tax data. The cryptographic stamp is not written among this data; instead it is inserted into a dedicated area at the top of the file called UBLExtensions.

This area is the standard way in UBL to add data not specified by the core standard. The digital signature and the cryptographic stamp are additional data of this kind, so they reside inside a single extension. The general structure looks like this:

<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
  <ext:UBLExtensions>
    <ext:UBLExtension>
      <ext:ExtensionURI>urn:oasis:names:specification:ubl:dsig:enveloped:xades</ext:ExtensionURI>
      <ext:ExtensionContent>
        <sig:UBLDocumentSignatures>
          <sac:SignatureInformation>
            <ds:Signature Id="signature">
              <!-- The digital signature and cryptographic stamp live here -->
            </ds:Signature>
          </sac:SignatureInformation>
        </sig:UBLDocumentSignatures>
      </ext:ExtensionContent>
    </ext:UBLExtension>
  </ext:UBLExtensions>

  <cbc:ProfileID>reporting:1.0</cbc:ProfileID>
  <cbc:ID>INV-2026-000128</cbc:ID>
  <cbc:UUID>3cf5ee18-ee25-4ea1-bf7e-3df0d5a5e1f2</cbc:UUID>
  <!-- The rest of the invoice data: seller, buyer, line items, tax -->
</Invoice>

Notice the order of the namespaces:

  • ext: the UBL extension, the outer container.
  • sig: andsac: the document signature elements in UBL.
  • ds: the standard XMLDSig elements, which are the heart of the signature.
  • xades: the XAdES elements that add the signed properties such as the signing time and the certificate digest.

Any mixing of these namespaces or of the element order fails validation. The Fatoora platform treats the file strictly, so the correct element in the wrong position is rejected as if it did not exist.

The difference between the body and the extension

Remember this rule: the invoice data is written once in the body. The cryptographic stamp is computed over this data and then inserted into the extension. This means that any change to the line items or the amounts after the stamp is generated makes the stamp non-matching, which is exactly what validation detects.

The technical reason behind this ordering is that the extension area is excluded from the computation of the invoice digest. If the signature value entered the digest that the signature itself signs, an impossible loop would arise: you need the signature value in order to compute the digest, and you need the digest in order to compute the signature. That is why the signature element is removed from the content before its digest is computed, via a Transform declared inside the reference. This is a subtle point that many developers get wrong in their first integration.

In practice, the body contains everything the accountant or the client reads: the seller’s name and tax number, the buyer’s data, the invoice line items with their prices and quantities, the tax rate and amount, and the total before and after tax. The extension contains what no one reads with the naked eye: the signature, the digests, and the certificate. The separation between the two layers is deliberate: the business data stays readable while the security layer sits above it without mixing into it.

Why UBLExtensions specifically

The UBL 2.1 standard defines the invoice structure precisely, but it does not specify the digital signature among the core elements. Instead of amending the standard, UBL provides an official extension mechanism via ext:UBLExtensions. The authority leveraged this mechanism to embed the signature according to the internationally recognized XAdES profile, rather than inventing a proprietary format. This decision makes phase two invoices compatible with international validation tools, and makes it easier for developers to rely on ready-made XMLDSig and XAdES libraries instead of building them from scratch.

The value of ext:ExtensionURI declared in the previous snippet is not optional. It tells any reader that the content of this extension is a signature of type enveloped XAdES, that is, a signature that lives inside the document itself rather than separately from it. Any other value, or its absence, makes validation tools unable to determine how to process the content.

The structure of the signature element: ds:Signature

Inside ds:Signature there are three main sections. Each section has a specific role, and knowing their roles clarifies where the invoice digest lives and where the digest of the signed properties lives.

<ds:Signature Id="signature" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <ds:SignedInfo>
    <ds:CanonicalizationMethod Algorithm=".../xml-exc-c14n#"/>
    <ds:SignatureMethod Algorithm=".../xmldsig-more#ecdsa-sha256"/>

    <!-- First reference: digest of the entire invoice -->
    <ds:Reference URI="">
      <ds:Transforms> ... </ds:Transforms>
      <ds:DigestMethod Algorithm=".../xmlenc#sha256"/>
      <ds:DigestValue>k3Pn...invoice-digest...=</ds:DigestValue>
    </ds:Reference>

    <!-- Second reference: digest of the signed properties -->
    <ds:Reference URI="#xadesSignedProperties" Type=".../XAdES#SignedProperties">
      <ds:DigestMethod Algorithm=".../xmlenc#sha256"/>
      <ds:DigestValue>9Qa2...properties-digest...=</ds:DigestValue>
    </ds:Reference>
  </ds:SignedInfo>

  <ds:SignatureValue>MEUCIQ...ECDSA-signature-value...</ds:SignatureValue>

  <ds:KeyInfo>
    <ds:X509Data>
      <ds:X509Certificate>MIID...CSID-certificate...</ds:X509Certificate>
    </ds:X509Data>
  </ds:KeyInfo>

  <ds:Object>
    <xades:QualifyingProperties> ... </xades:QualifyingProperties>
  </ds:Object>
</ds:Signature>

ds:SignedInfo: what is actually signed

The signature is not applied to the invoice directly. It is applied to the element ds:SignedInfo. This element contains two references, each reference storing a digest of a specific part:

  • The first reference with URI="" points to the entire invoice. The value of ds:DigestValue here is the invoice digest known as the Invoice Hash.
  • The second reference with URI="#xadesSignedProperties" points to the signed properties, that is, the certificate digest and the signing time.

With this binding, the invoice content, the issuer’s identity, and the signing time all become part of the signed value. Any change in any of them breaks validation.

Each reference inside SignedInfo has three sub-elements that must be understood together. ds:Transforms describes the transforms applied to the referenced part before its digest is computed, the most important of which are removing the signature element itself and applying canonicalization. ds:DigestMethod declares the digest algorithm, which is SHA-256 in phase two invoices. ds:DigestValue carries the output in Base64 encoding. The absence of any of these three, or a divergence of the algorithm from the approved one, makes validation compute a value different from the one stored, so the invoice is rejected.

Canonicalization and its decisive role

Before any digest is computed, the referenced part of the XML goes through a canonicalization process. The reason is that the same logical content can be written in different textual forms: leading whitespace, attribute order, blank lines, the way namespaces are declared. These differences do not change the meaning, but they change the text character by character, and therefore change the SHA-256 digest radically.

Canonicalization converts the XML into a single standard form before computation, thereby ensuring that the issuer and the authority compute the digest over exactly the same bytes. The approved algorithm is Exclusive Canonical XML declared in ds:CanonicalizationMethod. If the issuer’s system used a different canonicalization, it would compute a valid digest locally, but it would not match the one the authority computes, so the invoice is rejected with a digest error message even though the content is correct. This is one of the most frustrating sources in a first integration, and it requires literal adherence to the approved canonicalization algorithm.

ds:SignatureValue: the actual stamp

The element ds:SignatureValue is the cryptographic stamp in the precise sense. Its value is the result of signing SignedInfo with the private key of the CSID certificate using the ECDSA algorithm with SHA-256. This value is in Base64 encoding, cannot be produced without possession of the private key, and cannot be forged without breaking the cryptography.

The structure of the signature element ds:Signature
How the cryptographic stamp element is composed inside XML.
The stamp structure

lives inside ext:UBLExtensions

ds:SignedInfo contains two references (Reference) for the digests

ds:SignatureValue = the actual stamp value

ds:KeyInfo/X509Certificate = the CSID certificate

xades:SignedProperties (signing time and certificate digest)

The actual stamp lies in SignatureValue inside the signature structure.

ds:KeyInfo: the public certificate

carries ds:KeyInfo Inside ds:X509Certificate the public CSID certificate in Base64 encoding. This certificate allows the Fatoora platform to extract the public key and verify the signature value without needing the private key. The details of certificate encoding and compliance are explained in the reference The cryptographic stamp identifier CSID.

A subtle point in certificate encoding: the value inside X509Certificate is DER content in Base64 encoding without PEM headers and footers, and without line breaks. Many developers copy the certificate as it appears in a PEM file with the begin and end lines, so its reading fails because the parser expects pure Base64. The rule: strip the headers, footers, and blank lines, and keep a continuous Base64 string.

Compliance certificate versus production certificate

At the integration level you must distinguish between two types of CSID certificates, because they appear in the same position inside X509Certificate but they serve two different phases. The Compliance CSID certificate is issued in the simulation environment to test your integration before going live. The Production CSID certificate is issued after passing the test, and it is the one that signs the real invoices reported to the authority.

The common mistake: signing a production invoice with a compliance certificate, or vice versa. The structure is the same, but the authority rejects the invoice because the certificate type does not match the environment. When building the integration, make sure your system switches the stored certificate when moving from simulation to production, and does not keep the simulation certificate after actual go-live.

The signed properties: xades:SignedProperties

The section that adds the XAdES dimension on top of XMLDSig is xades:SignedProperties. Here the certificate digest and the signing time live, and they are the two elements referenced by the second reference in SignedInfo.

<xades:SignedProperties Id="xadesSignedProperties">
  <xades:SignedSignatureProperties>

    <!-- Signing time -->
    <xades:SigningTime>2026-06-24T11:42:18Z</xades:SigningTime>

    <!-- Digest of the signing certificate and its issuer -->
    <xades:SigningCertificate>
      <xades:Cert>
        <xades:CertDigest>
          <ds:DigestMethod Algorithm=".../xmlenc#sha256"/>
          <ds:DigestValue>ZmI3...certificate-digest...=</ds:DigestValue>
        </xades:CertDigest>
        <xades:IssuerSerial>
          <ds:X509IssuerName>CN=ZATCA-CA, ...</ds:X509IssuerName>
          <ds:X509SerialNumber>379...</ds:X509SerialNumber>
        </xades:IssuerSerial>
      </xades:Cert>
    </xades:SigningCertificate>

  </xades:SignedSignatureProperties>
</xades:SignedProperties>

Here three values are bound to the signature:

  • xades:SigningTime: the signing timestamp in UTC format. It proves the moment the invoice was stamped, and it is part of the signed value so it cannot be changed later.
  • xades:CertDigest: the SHA-256 digest of the certificate used, binding the signature to a specific CSID certificate.
  • xades:IssuerSerial: the name of the certificate issuer and its serial number, proving that the certificate was issued by an approved authority.

The practical importance: the second reference in SignedInfo computes a digest of this entire element. With this, the certificate identity and the signing time become an integral part of the stamp, not merely attached metadata.

The timestamp here differs from the invoice timestamp in the body. The body timestamp (cbc:IssueDate andcbc:IssueTime) is the commercial invoice date. As for xades:SigningTime it is specifically the moment the stamp is generated. In most cases they are close, but the difference matters technically: the first is business data, and the second is security evidence of when the system stamped the invoice. The SigningTime must be in UTC format with the Z suffix, as using a local time without a time zone causes confusion in validation.

How each digest is computed step by step

To fix the picture at the implementation level, here is the sequence that any signing system builds for each of the two digests:

  1. The invoice digest (first reference): The system takes the invoice document, removes from it the element ds:Signature via the declared transform, applies canonicalization, then computes SHA-256 over the output. The resulting value is placed in the first DigestValue .
  2. The properties digest (second reference): The system takes the element xades:SignedProperties alone, applies canonicalization, then computes SHA-256. The resulting value is placed in the second DigestValue .
  3. The signature value: After the two digests are complete inside SignedInfo, the system applies canonicalization to the entire SignedInfo , then signs it with ECDSA using the private key. The output in Base64 encoding is placed in SignatureValue.

This ordering is not reversible: the signature value cannot be computed before the two digests are complete, because the SignedInfo that is signed contains them. Any system that attempts to sign SignedInfo before filling in the digests produces a meaningless signature, and validation fails immediately.

The three values in the cryptographic stamp
What each of the three signature values covers.
The value What it covers
First DigestValue Hash of the invoice content
Second DigestValue Hash of the signed properties
SignatureValue The actual cryptographic stamp
Invoice hash + properties hash + the stamp prove authenticity and integrity.

Linking the previous invoice hash: the integrity chain

The Previous Invoice Hash does not live inside ds:Signature, but rather in the invoice body within additional document properties. However, it is included in the invoice digest, so the stamp protects it.

<cac:AdditionalDocumentReference>
  <cbc:ID>PIH</cbc:ID>
  <cac:Attachment>
    <cbc:EmbeddedDocumentBinaryObject mimeCode="text/plain">
      NWZh...previous-invoice-hash...=
    </cbc:EmbeddedDocumentBinaryObject>
  </cac:Attachment>
</cac:AdditionalDocumentReference>

With this linking, a chain is formed: the hash of each invoice points to the invoice that preceded it. Any attempt to delete an invoice from the middle or insert an invoice retroactively breaks the chain, so the flaw appears at validation. The first value in the chain (the first invoice) uses a standard value for the initial hash.

Representing the stamp inside the QR code

The QR code printed on the simplified (B2C) invoice is not a random image. It is a TLV (Tag-Length-Value) encoded string in Base64, containing specific fields. The stamp fields within it come in the last tags:

Tag 1  Seller name
Tag 2  Seller tax number
Tag 3  Invoice timestamp
Tag 4  Invoice total including tax
Tag 5  VAT amount
Tag 6  Invoice hash (Invoice Hash)
Tag 7  Digital signature value (ECDSA)
Tag 8  Public key of the signing certificate
Tag 9  Authority stamp on the certificate (for simplified invoices)

Tags 6 through 9 are the phase two extension. They carry the essence of the cryptographic stamp into the QR code so that any approved validation application can read the simplified invoice and confirm the validity of its stamp without referring back to the full XML file.

TLV encoding means that each field is written in three consecutive parts: the tag number (Tag), the length of the value (Length) in bytes, then the value itself (Value). With this, the reader can know where each field ends and the next begins without delimiters. The resulting string is Base64 encoded and then converted into a QR image. Any flaw in computing the length shifts the rest of the fields so the application reads wrong values, which is why the length must be computed in bytes, not characters, a difference that shows up clearly with multi-byte Arabic text.

The difference between the tax invoice (B2B) and the simplified invoice (B2C) is reflected in the QR code. The simplified invoice is generated and delivered to the customer immediately and reported to the authority within 24 hours, so its code carries all nine tags, including the authority stamp on the certificate in the ninth tag. The tax invoice passes through the authority for clearance before being delivered to the buyer, so some details of code generation differ according to the clearance path.

The stamp lifecycle: from generation to archiving

To complete the picture at the integration level, here is the lifecycle of the stamp within a single invoice from the moment of creation until archiving:

  1. Build the body: The system assembles the invoice data into UBL elements, computes the totals and tax, and inserts the previous invoice hash into the element PIH.
  2. Compute the invoice digest: After the body is complete, the system canonicalizes the document and computes SHA-256 to obtain the invoice digest.
  3. Build the signed properties: The system fills in SigningTime the certificate digest and its reference, then computes the digest of this element.
  4. Signing: The system builds SignedInfo with the two digests, canonicalizes it, then signs it with ECDSA to fill in SignatureValue.
  5. Assembly: The system inserts the entire ds:Signature inside ext:UBLExtensions, and generates the QR code from the required values.
  6. Submission: The file is sent to the Fatoora platform for clearance (B2B) or reporting (B2C).
  7. Archiving: The system stores the signed invoice and its hash, so that its hash becomes the value of PIH for the next invoice, and the chain continues.

Every step depends on the one before it. Swapping the order or skipping a step produces an invalid stamp. This cycle is what the accounting system builds behind the scenes in fractions of a second for every invoice you issue.

QR code fields in TLV encoding
The nine fields carried by the QR code in phase two.
QR fields (TLV)

1-5: seller name, tax number, time, total, tax

6: invoice hash

7: the cryptographic stamp (signature)

8: the public key

9: the authority stamp

Fields 6–9 are the phase two additions associated with the stamp.

The field-level validation cycle

When the Fatoora platform receives the file, it runs a validation sequence on the elements we described. Understanding this sequence helps diagnose the reason any invoice is rejected:

  1. Recompute the invoice digest and compare it with the value of DigestValue in the first reference. A mismatch means the content changed after signing.
  2. Recompute the digest of the signed properties and compare it with the second reference. A mismatch means tampering with the signing time or the certificate reference.
  3. Verify the signature value in SignatureValue using the public key extracted from the certificate. A failure here means the key does not match the signature.
  4. Verify the validity of the certificate: is it issued by an approved authority? Is it still valid? An expired CSID certificate fails this step.
  5. Verify the hash chain: does the previous invoice hash point to the last actual invoice?

Any step that fails returns an error message from the authority. The messages are sometimes technical and cryptic, so understanding the element responsible for each step helps reach the root quickly.

The most common integration errors at the XML level

  • Element order: UBL is sensitive to the order of children inside a single element. A correct element in the wrong position is rejected.
  • Canonicalization: using a canonicalization algorithm different from the approved one changes the computed digest so the match fails.
  • Namespaces: declaring ds: or xades: in the wrong position breaks the digest references.
  • Certificate encoding: inserting the certificate with line breaks or PEM headers inside X509Certificate fails its reading.

Mapping the error message to the responsible element

When an invoice is rejected, the Fatoora platform returns a message indicating the type of flaw. Mapping the message type to the responsible element shortens diagnosis time. Here is a practical map:

  • Document digest error: review the first DigestValue , the canonicalization algorithm, and the transforms. The cause is usually a different canonicalization or a change made to the body after computation.
  • Properties digest error: review the first DigestValue the second and the content of xades:SignedProperties. The cause is usually a change in SigningTime or the certificate reference after computation.
  • Signature value error: review the match between the signing private key and the certificate embedded in KeyInfo. A key that does not match the certificate produces a signature that cannot be verified.
  • Certificate error: review the validity of the CSID, its type (compliance versus production), and its issuer in IssuerSerial.
  • Hash chain error: review the value of PIH in the body and its match with the last actual invoice.

The advantage of this map is that it turns a cryptic error message into a specific position in the XML from which to start. Instead of reviewing the entire file, you go straight to the responsible element.

How Qoyod handles building the cryptographic stamp on your behalf

Everything above describes the manual work required to build a signed invoice from scratch. With the electronic invoicing software from Qoyod you do not write this structure yourself. The system generates the UBL 2.1 file, builds the signature element, computes the digests, and places the stamp in its correct position automatically.

At the field level, Qoyod handles the following:

  • Generating an XML file compliant with UBL 2.1 with all its elements in the correct order.
  • Managing the CSID certificate and storing its private key securely, and signing every invoice with ECDSA without manual intervention.
  • Computing the invoice digest and the digest of the signed properties and inserting them into the two correct reference elements.
  • Building the previous invoice hash (PIH) chain automatically between each invoice and the one before it.
  • Generating the QR code in TLV encoding, including the stamp tags 6 through 9.
  • Instant integration with the Fatoora platform: clearance of B2B invoices (Clearance) and reporting of B2C invoices within 24 hours.
  • A simulation environment for testing invoices before actual go-live, so you make sure validation succeeds without risk.

Your responsibility remains: registering the CSID certificate with the authority through its portal (Qoyod guides you step by step), and filing and paying the tax return through the authority. Generating the correct stamp structure is not your task, but the system’s task.

Start today

Cryptographically stamped invoices without writing a single line of XML

Qoyod generates the UBL 2.1 structure, builds the cryptographic stamp, and connects you to the Fatoora platform automatically, so you issue phase-two-compliant invoices from day one.

Start your free trial and issue stamped invoices

Frequently asked questions at the integration level

Where exactly is the stamp value written inside the XML?

In the element ds:SignatureValue Inside ds:Signature, and the latter lives inside ext:UBLExtensions at the top of the invoice file. This value is in Base64 encoding and is the result of signing ds:SignedInfo with the private CSID key.

What is the difference between the invoice hash and the signature value?

The invoice hash (DigestValue in the first reference) is simply the SHA-256 value of the invoice content. The signature value (SignatureValue) is the result of asymmetric ECDSA encryption over the entire SignedInfo . The hash proves integrity, and the signature proves identity and integrity together.

What is the approved signing algorithm?

ECDSA signature on the P-256 curve with the SHA-256 hash function, that is, ecdsa-sha256 in the ds:SignatureMethoddeclaration. The digests are computed with SHA-256 within ds:DigestMethod.

Why is my invoice rejected even though the elements are present?

The most common causes: a canonicalization algorithm different from the approved one, a wrong order of children inside a UBL element, or a certificate embedded with PEM headers. Each one changes the computed digest or breaks the reading of the certificate, so validation fails even though the tags appear complete.

Do I need to understand this structure in order to issue a valid invoice?

No, if you are using an approved system such as Qoyod. This document is aimed at developers and integration engineers who build or audit the integration. The business owner never touches XML at all; instead they issue the invoice from the interface and the system builds the stamp.

Where do I find the conceptual, not technical, explanation?

In the article The cryptographic stamp in the electronic invoice which explains what the stamp is, why the authority mandated it, and how it protects your invoices, without descending to the tag level.

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.