If the Fatoora platform rejected your invoice and returned error code 1003, the message is telling you that the invoice hash you submitted does not match the hash the platform computed from the same content. In plainer terms: the authority recomputed the invoice fingerprint using the SHA-256 algorithm on the XML version it received, and got a value different from the one you placed inside the document. This page explains the error at its root: what an invoice hash means, why it comes out mismatched, how to read the rejection message, how to pinpoint exactly where the fault lies, how to fix it and resubmit, and how to prevent it from recurring for good.
This guide is part of the ZATCA e-invoicing error reference, and within the series invoice hash errors, inside the ecosystem of Qoyod e-invoicing. The intended audience is the developer, integration analyst, or accountant who manages the technical implementation and wants a precise understanding of the cause, not just a surface step that hides the underlying problem.
What error code 1003 means exactly
Error code 1003 is a rejection at the content-integrity level. Every electronic invoice in Phase Two carries a digital fingerprint called the hash, a fixed-length cryptographic output computed from the invoice content using the SHA-256 algorithm. The idea is simple at its core: any change in a single byte of the document changes the entire hash. That is why the platform uses this fingerprint to confirm that the invoice it received is the same one your establishment signed, with no tampering along the way.
When the invoice is received, the platform recomputes the hash from the received XML, then compares the result with the value you inserted inside the document. If the two values match, the invoice passes this gate. If they differ, the platform halts the invoice and returns code 1003. The practical meaning of the rejection is that the version whose hash you computed is not the same version the platform read, or that the calculation method itself differed between you and the platform.
It is important not to confuse this code with signature errors. Error 1003 says the fingerprint does not match the content, meaning a problem in the hash before signing or in the version that was signed. Signature errors, on the other hand, say the cryptographic seal itself is invalid or does not match the certificate. The distinction is subtle but decisive: in 1003 the problem is in “what you computed,” while in signature errors the problem is in “what you signed with.” Handling the error in the right place saves you long repair cycles.
Why the authority requires a hash for every invoice
Computing a fingerprint for every invoice may seem like extra technical overhead, but it serves a clear purpose in the design of Phase Two of e-invoicing. The hash is the cornerstone of proving that the invoice was not altered after issuance, and that it is linked to the invoice preceding it in a connected chain where no link can be tampered with without exposure. This delivers content integrity, non-duplication, and non-repudiation all at once.
The SHA-256 algorithm produces a fixed-length fingerprint regardless of invoice size, and it is a one-way function: it is easy to compute the fingerprint from the content, practically impossible to recover the content from the fingerprint, and practically impossible for two different contents to produce the same fingerprint. These three properties are what make the hash a reliable verification tool. When the platform recomputes the fingerprint and compares it against what you sent, it is effectively confirming that the bytes it read are the bytes you intended to adopt, character by character.
And here lies the reason for the strictness of error 1003: the platform does not compromise on fingerprint matching, because any leniency opens a door to tampering. So if your fingerprint differs from its fingerprint by even a single character, it halts the invoice immediately. This means solving the error is not about convincing the platform to accept your value, but about ensuring you compute the fingerprint the same way and on the same content you send.
How error 1003 relates to the invoice hash chain
Every invoice in Phase Two carries, in addition to its own hash, a reference to the hash of the invoice preceding it, forming an interconnected chain. This linkage protects the issuance sequence from deletion or insertion. And because error 1003 concerns the hash of the current invoice, ignoring it may break the chain on subsequent invoices if your system adopts a wrong fingerprint as the previous link.
In practice, if you compute an invoice fingerprint the wrong way and adopt it internally, then build the next invoice on that fingerprint, you may receive cascading rejections even after you think you have fixed the problem. That is why the rule is to adopt only the fingerprint that matches what the platform accepts, not the fingerprint your system computed locally, when building the next link. This discipline prevents a single error from turning into a chain of successive rejections that consume your time tracing a branching trail.
Why the hash comes out mismatched
The hash is a function that is extremely sensitive to input. Changing a single character, an extra space, or the order of two elements is enough to produce a completely different fingerprint. In e-invoicing integration, the causes of mismatch concentrate in three clear families, and each family has a different root and a different fix.
Cause one: computing the hash on a different XML version
This is the most common cause. You compute the hash on one version of the invoice, then send another version that differs from it even slightly. This happens when your system adds a field, reformats amounts, updates the timestamp, or adds leading whitespace after the moment the fingerprint was computed and before sending. The result is that the platform reads a version that does not match what you hashed. The golden rule here: compute the hash on the exact final bytes you will send, not on an intermediate version.
Cause two: mixing Hex and Base64 encoding
The invoicing specification requires the hash value to be represented in a specific encoding when it is inserted into the invoice. The common mistake is for your system to generate the hash in hexadecimal (Hex) format while the platform expects it in Base64, or vice versa. The numeric value is the same, but its text representation differs, so the comparison fails because the two strings do not match literally. This is a hidden cause because the fingerprint looks “correct” to the eye, but it is written in a language the platform does not read.
Cause three: missing canonicalization
Before computing the hash, the XML must be transformed into a standard, unified form through the process of canonicalization. This process unifies whitespace, attribute order, and namespace declarations, so that every system produces the same bytes for the same logical content. If you compute the hash on non-canonicalized XML, or apply a canonicalization rule different from the approved one, your fingerprint comes out different from the platform’s fingerprint even though the logical content is the same. This cause confuses developers a lot because the invoice looks sound to the eye, and the entire difference is in bytes you cannot see.
The three causes of hash mismatch
Hashing a different XML version than what was sent
Mixing hex encoding with Base64
Missing C14N canonicalization before hashing
How to read the rejection message
When the invoice is rejected, the platform returns a response carrying the clearance status and a list of validation errors. Each error carries a code, a category, and a message. Reading this response precisely is your first step, because it tells you whether the halt is due to the hash alone or due to other accompanying errors that may obscure the real cause. Do not settle for looking at the overall status; examine the error list item by item. The block below is an illustrative example of a rejection response caused by an invoice hash mismatch. Copy it and compare its structure with the response your system returns, and start your diagnosis from the code and category, not from the message alone.
The keys that matter to you when reading:
clearanceStatuswith a value ofNOT_CLEAREDmeans clearance did not take place, so the invoice was not approved and may not be delivered to the buyer.codewith a value of1003is the precise definition of the cause, and the categoryInvoice hash validationconfirms that the problem is in the hash, not in a data field.- The text message states explicitly that the inserted hash does not match the result of recomputing SHA-256 on the sent document, and this is direct guidance to where the fault lies.
clearedInvoicewith a value ofnullconfirms there is no corresponding approved invoice, so do not build any subsequent step on its existence.
How to compare the two fingerprints in practice
The clearest way to settle the cause of 1003 is to place the two fingerprints side by side: the fingerprint your system inserted into the invoice, and the fingerprint you produce when you manually recompute on the sent bytes. When comparing, you face three cases, and each case points to a different cause family.
The first case is that your recomputed fingerprint matches the fingerprint the platform produced, and both differ from the value inserted in the invoice. This means the inserted value is stale or was computed on an earlier version, so the content changed after the moment of insertion. The second case is that your recomputed fingerprint matches the inserted value, and both differ from the platform’s fingerprint. Here the problem is in canonicalization or encoding: you are consistent with yourself but you compute in a way that does not match the platform’s way. The third case is that all three fingerprints differ, which means more than one overlapping fault, so start by fixing canonicalization and then re-examine.
This methodical comparison saves hours of guesswork. Instead of random edits and repeated resubmissions, you move directly to the layer responsible for breaking the match, so you fix the source, not the display. And once you establish that your fingerprint matches the platform’s fingerprint on the same content, error 1003 is behind you for good.
Edge cases that produce error 1003
Alongside the three major causes, there are finer edge cases worth noting because they appear suddenly after a period of stability:
- Character encoding differences. Saving the XML in an encoding other than UTF-8, or inserting a byte order mark (BOM) at the start of the file, changes the bytes, so the fingerprint differs with no visible change in the content.
- Line-ending normalization. Converting line endings between systems may alter invisible bytes, especially when the file passes through an intermediate tool that rewrites it.
- Automatic reserialization in libraries. Some XML processing libraries reorder attributes or add whitespace on save, breaking the match if this happens after the fingerprint is computed.
- Production environment differing from test. A different library version between the two environments may apply different canonicalization, so the test passes and production fails for the same reason without any change to your code.
The common thread across all these cases is that the logical content of the invoice is sound, and the fault is in bytes the eye cannot see. That is why diagnosis always stays at the byte level, not at the level of visible fields.
Where the hash is placed inside the invoice
The hash value is not inserted in any random place. The fingerprint is carried in a dedicated element within the extensions block in the document header, alongside the digital signature and the stamp. A clear understanding of its location helps you determine whether the value is missing, in the wrong place, or computed on a version that does not match what you sent.
The logical path of the hash inside the invoice passes through three layers: the actual invoice content, then the canonicalization process that unifies its form, then computing SHA-256 on the result, and finally inserting the encoded value in its place. Any disconnect between these layers is the origin of error 1003.
The correct path for computing the hash
Final XML
C14N canonicalization
SHA-256 on the bytes
Encode and insert
How to pinpoint exactly where the fault lies
Before any edit, diagnose the cause among the three families so you do not treat the display without the root. Follow this logical sequence:
- Capture the bytes actually sent. Log an exact copy of the request body that leaves your system for the platform, not the version you built in memory. Many 1003 cases are exposed here immediately when you find a difference between the two versions.
- Recompute SHA-256 manually. Apply the hash to the same canonicalized XML, and compare the result with the value inserted in the invoice. If they differ, the problem is in the computation or canonicalization, not in transmission.
- Check the encoding. Verify that the hash value is written in the approved encoding, not in hexadecimal where Base64 is expected or vice versa. This check settles the second cause in seconds.
- Review the canonicalization. Make sure you apply the required canonicalization rule before computing, and that namespace declarations, attribute order, and whitespace are unified. A difference in the canonicalization algorithm is a silent but recurring cause.
- Isolate the moment of computation. Make sure no field changes after the hash is computed and before sending, especially timestamps and identifiers generated on the fly.
With this sequence you move from “the invoice is rejected” to “I know exactly which layer broke the match,” and that is half the solution.
How to fix the invoice and resubmit
After identifying the cause, the fix is straightforward but subject to one rule that is never broken: any change in the content changes the hash, so you may not resubmit the rejected invoice as is after touching it. The practical steps:
- Fix the moment of computation on the final bytes. Build the XML in full, apply canonicalization, compute SHA-256 on the result, and insert the value, then never touch the document again before sending.
- Unify the encoding. Fix the encoding layer to write the hash value in the format approved by the specification, and set all test tools to read in the same format.
- Apply the correct canonicalization. Adopt the required canonicalization algorithm before computing, and fix it in the production pipeline so it does not change from one environment to another.
- Regenerate and re-sign. After correcting the content, regenerate the invoice, recompute the hash, then sign with the CSID certificate before sending it to clearance. Do not reuse an old fingerprint or signature.
- Send and verify. Send the corrected version, and make sure that
clearanceStatuscame back with an approved value and that the error list is empty.
The rule in brief: compute on what you will send, sign on what you computed, and send what you signed without touching it. This sequence alone closes the door on error 1003.
How to prevent error 1003 from recurring
Prevention is far cheaper than repair, and the hash error is one of the most preventable errors because it results from engineering discipline, not from missing data. Fix these controls in the production pipeline:
- Make the computation pipeline a single, indivisible stage. Canonicalization, then hashing, then signing, then sending on the same bytes, with no editing step in between.
- Add a local match test. Before sending, recompute the hash locally and compare it with the inserted value, and refuse to send if they differ. This catches the error before it reaches the platform.
- Unify the encoding in one place. Make a single function responsible for encoding the hash across the whole system, so Hex format does not leak into a place that expects Base64.
- Fix the canonicalization algorithm across environments. Make sure the test and production environments use the same canonicalization rule, since a difference is a recurring cause of errors that appear suddenly after deployment.
- Monitor the sent version. Log the bytes actually leaving so you can compare immediately on any future rejection.
Pre-submission checklist
Hash on the final bytes
Apply C14N
Correct encoding (no hex instead of Base64)
No editing after hashing
Re-sign after any edit
Common mistakes when handling error 1003
Many establishments prolong the problem with wrong handling, spinning in a loop of repeated rejection. Avoid these six pitfalls:
- Resubmitting without regenerating. You think adding the field is enough, so you resubmit the same document. But the edit changed the content, and its old fingerprint is no longer valid, so the rejection repeats.
- Fixing the inserted value instead of the computation. You paste a new fingerprint value manually into the document without recomputing it on the final bytes, so you treat the display and leave the root untouched.
- Ignoring canonicalization. You focus on the visible content and forget that canonicalization precedes computation, so the fingerprint stays different despite the fields being sound.
- Mixing up the test and production environments. You test on one version of the library and deploy on another, so the test succeeds and production fails for the same technical reason.
- Relying on a local fingerprint in the next link. You build the next invoice on a fingerprint the platform did not accept, so the rejection propagates to the chain of subsequent invoices.
- Neglecting to log the sent bytes. You diagnose from the version you built in memory, not from the version that actually left, so you miss the differences that arise on sending.
The overarching rule is to treat error 1003 as a problem in the whole production pipeline, not as a defect in a single invoice. Once you tune the pipeline one time, the error disappears from all invoices, not just from one.
How Qoyod helps you avoid error 1003
Qoyod is an accounting system compliant with Phase Two of e-invoicing, and it handles the hashing and signing layer on your behalf so you never reach this error in the first place:
- Qoyod generates the invoice file according to the UBL 2.1 specification automatically, so you do not write the XML by hand and do not interfere in its structure between computation and sending.
- Qoyod computes the invoice hash with the SHA-256 algorithm on the exact final version you send, within a single pipeline that does not separate computation, signing, and sending.
- Qoyod applies the approved canonicalization and encoding internally, so there is no mixing of Hex and Base64 and the canonicalization rule does not differ from one environment to another.
- Qoyod manages the CSID certificate automatically and stores the invoice hash chain to verify its integrity, so invoices stay linked and matched with what the platform reads.
- Qoyod handles instant clearance for business invoices and reporting within 24 hours for individual invoices, so your invoices pass the content-integrity gate without rejection.
Issue your invoices without hash errors
Let Qoyod handle hashing, canonicalization, signing, and clearance in a single pipeline compliant with Phase Two, so error 1003 never reaches you and you do not waste time tracing fingerprints.
Start your free trial and issue invoices compliant with the authority
Frequently asked questions
What is the difference between hash error 1003 and signature errors?
Error 1003 says the inserted hash value does not match the result of recomputing SHA-256 on the sent document, meaning a problem in “what you computed.” Signature errors, on the other hand, say the cryptographic seal itself is invalid or does not match the certificate, meaning a problem in “what you signed with.” Treat each one in its own layer.
Do I resubmit the rejected invoice as is after correcting the hash?
No. Any change in the content changes the hash, so you must regenerate the document, recompute the fingerprint, and sign it with the CSID certificate before resubmitting to clearance. Do not reuse an old fingerprint or signature.
Why does the invoice look sound to the eye and yet be rejected with error 1003?
Because the hash is computed on the bytes, not on the meaning. Leading whitespace, a different attribute order, or an extra namespace declaration are all invisible to the eye but change the fingerprint. The cause is usually the absence of unified canonicalization before computing.
Could the cause simply be Hex encoding instead of Base64?
Yes, and it is a common cause. The numeric value of the hash may be correct, but representing it as text in a format other than the approved one makes the text comparison fail. Unify the encoding in a single function for the whole system.
How do I make sure the version I hashed is the same one I sent?
Log the bytes actually leaving your system, then recompute SHA-256 on them and compare the result with the inserted value. If they differ, there is a step editing the document after computation, usually a timestamp or an identifier generated on the fly.
How do I prevent error 1003 from recurring in the future?
Make canonicalization, hashing, signing, and sending a single, indivisible pipeline, and add a local match test that refuses to send if the recomputed fingerprint differs from the inserted one. Qoyod builds this pipeline automatically, so the error never reaches you in the first place.