BR-KSA-26Previous invoice hash attachment carries no payload element
Mutabiq checks Saudi ZATCA Phase 2 e-invoice XML before you submit it: pre-validation against published ZATCA requirements, not an approval by the authority. This page explains one rule, BR-KSA-26.
Five checks a month on the Free plan. The file is checked in memory and is never stored.
The message ZATCA prints
[BR-KSA-26]-If the invoice contains the previous invoice hash (KSA-13), this hash must be base64 encoded SHA256. The hash shall be computed using the following method as described in the ds:transforms block in the XML Invoice Specifications: 1. Remove the <Invoice><ext:UBLExtensions/> block 2. Remove the <Invoice><cac:AdditionalDocumentReference/> block where <cbc:ID/> = QR 3. Remove the <Invoice><cac:Signature/> block 4. Canonicalize the Invoice using the C14N11 standard 5. Hash the resulting string using SHA256 to a binary object 6. Base64 encode the binary object to generate the digest value For the first invoice, the previous invoice hash is "NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==", the equivalent for base64 encoded SHA256 of "0" (zero) character.
Also cited in this report
BR-KSA-61
[BR-KSA-61]-Previous invoice hash (KSA-13) must exist in an invoice.
What Mutabiq reports
One entry for each way this rule can be reported.
Reported as
What kind of value was found — empty, not_15_digits and the like — never the value itself.
Previous invoice hash attachment carries no payload element
Reported aspayload_element_missingthe payload element is missing
What is wrong
The cac:Attachment inside the PIH document reference carries no cbc:EmbeddedDocumentBinaryObject, so the attachment holds no previous invoice hash. ZATCA flags the document under BR-KSA-26 as a warning, which does not by itself reject the document. You are likely to see this alongside BR-KSA-61, and that is correct rather than a duplicate: ZATCA ships two separate assertions over this one element - BR-KSA-61 anchored at the document root and BR-KSA-26 anchored on the attachment itself - and runs both, so a single defect legitimately produces two findings and two codes. Fixing the element clears both. One thing this rule does not do, despite what its message says: BR-KSA-26's published message states that the hash must be a base64-encoded SHA-256, but the check behind it tests only that the element exists, is not empty and declares a media type. It never decodes the value and never measures it. Whether the value really is a base64 SHA-256 is reported separately in this report, as a risk rather than as a rejection.
What to check in your ERP
Inside the PIH reference the structure is cac:Attachment and then cbc:EmbeddedDocumentBinaryObject inside it, with the base64 value as that element's text content. Because this rule is anchored on the attachment, it reports once per PIH attachment in the document - so an export that writes more than one PIH block will report each one that is wrong. The usual causes are a template that emits the attachment wrapper and fills it from a second step that did not run, and a hash written one level too high, directly under cac:Attachment or under the reference itself.
Previous invoice hash attachment is empty
Reported aspayload_emptythe payload is empty
What is wrong
The cbc:EmbeddedDocumentBinaryObject inside the PIH attachment is present but has no value, so the attachment states no previous invoice hash. ZATCA flags the document under BR-KSA-26 as a warning, which does not by itself reject the document. Expect this alongside BR-KSA-61 for the same element: ZATCA ships two assertions over it, one anchored at the document root and this one anchored on the attachment, and it runs both, so one empty value produces two findings and two codes. Note also what this rule does not check. BR-KSA-26's published message says the hash must be a base64-encoded SHA-256; the check behind it tests existence, non-emptiness and a media type, and nothing about the value's content. The shape of the value is reported separately, as a risk rather than as a rejection.
What to check in your ERP
The element is written and the value is not, so the block is produced unconditionally while the hash behind it is missing at the moment the XML is built. Check where the previous document's digest is kept between invoices: this commonly appears when the chain is held in memory and lost on restart, when the first document after a configuration change finds no predecessor recorded, or when the hash is computed after serialisation rather than before it. The first invoice of a chain is not a blank either - the XML Implementation Standard publishes the value to use there.
Previous invoice hash attachment is not declared text/plain
Reported asnot_text_plainnot declared text/plain
What is wrong
The cbc:EmbeddedDocumentBinaryObject inside the PIH attachment does not carry a mimeCode attribute of text/plain. SDK 3.4.8 flags the document under BR-KSA-26 as a warning, not a rejection: the finding is reported without failing the document by itself. The value is compared after whitespace is normalised and exactly otherwise: application/octet-stream, base64Binary, an empty attribute and an absent one each fail. Expect this alongside BR-KSA-61, which tests the same element from the document root; ZATCA runs both assertions, so one wrong media type produces two findings and two codes. And note the limit of this rule despite its message: BR-KSA-26 says in words that the hash must be a base64-encoded SHA-256, but the check tests only existence, non-emptiness and this media type. Whether the value is a hash at all is reported separately, as a risk rather than as a rejection.
What to check in your ERP
Set mimeCode to text/plain on cbc:EmbeddedDocumentBinaryObject itself, not on the cac:Attachment above it. Do not derive the value from the content: the payload is base64, so application/octet-stream reads as the more accurate description and draws this note. The usual causes are a generic attachment helper carrying its own default media type, a value read from a document-management table, and a template that omits the attribute entirely. The QR attachment needs the same declaration, so check both blocks together.
The clause that runs
Quoted from the validation-rule stylesheet ZATCA publishes, at the file and line named below. The executed guard is what the check runs; the reported test is what it prints when it fails, and the two are not always the same.
Executed guard
not(exists(cbc:EmbeddedDocumentBinaryObject) and (cbc:EmbeddedDocumentBinaryObject) != '' and (cbc:EmbeddedDocumentBinaryObject[normalize-space(@mimeCode) = 'text/plain']))
Reported test
exists(cbc:EmbeddedDocumentBinaryObject) and (cbc:EmbeddedDocumentBinaryObject) != '' and (cbc:EmbeddedDocumentBinaryObject[normalize-space(@mimeCode) = 'text/plain'])
As shipped in ZATCA SDK 3.4.8: the copy quoted here is, byte for byte, the file that release ships. The date in the file name is the name ZATCA gives the file; the rules in it are those of SDK 3.4.8.
A worked example
A synthetic document from the measured test corpus that trips this rule, shown where it differs from one that passed. Mutabiq's report names the class of the value, never the value itself.