This guide is for developers and integration teams building the submission pipeline for the electronic invoice to the Fatoora platform as part of e-invoicing in Saudi Arabia. It has one specific goal: the complete, practical recipe for submitting a single invoice from the developer’s side, step by step, from the moment the invoice data exists in your system until you store the copy cleared or reported by the Authority.
We do not repeat here the explanation of every header and every endpoint on its own; that is detailed in other guides we reference in their places. What we do is connect the steps together into one executable sequence: build the UBL document, sign it with the Cryptographic Stamp Identifier (CSID), encode it in Base64, compute the hash value (invoiceHash), choose the clearance or reporting path based on the invoice type, send the request, parse the response, then store the result. Each step depends on the one before it, and any disorder in the sequence produces a rejection from the Authority.
Before you start, make sure you have finished the onboarding phase and obtained a Production CSID for your device. If you are still in the testing environment, review API integration with the Fatoora platform which explains the move from compliance to production, then come back here to build the actual submission pipeline.
What do you prepare before the first submission?
Before you write the first line of the submission pipeline, verify that you have four things ready. The first is the Production CSID for every device that issues invoices, because signing without a production identifier cannot be completed. The second is the device key and its secret, on which you later build the request authentication header.
The third is a reliable library to build XML according to the UBL 2.1 schema, because composing the document manually with text strings is a common source of errors. The fourth is a cryptography library that supports digital signing and the hashing algorithm per the specification. Do not write these tools from scratch, because their errors surface late, at rejection, not at writing time.
Verify your working environment as well. Always start in the testing environment with the Compliance CSID, and send test invoices until you prove that your pipeline builds the document, signs it, encodes it, and computes its hash correctly. Do not move to production until the pipeline succeeds end to end in testing. The move from compliance to production is a step with its own rules, explained in API integration with the Fatoora platform.
Finally, prepare a storage layer for issued invoices before you send the first one. You will need to save the unique identifier, the invoice counter, and the hash value for each invoice as soon as it is issued, because they are inputs for the next invoice. Whoever postpones building the storage layer later discovers they broke the hash chain because they did not save the previous invoice’s hash.
The big picture: seven steps between the invoice data and the result
Submitting a single invoice is not one isolated HTTP request, but a chain of transformations on the document itself. It starts with simple business data (seller, buyer, line items, tax) and ends with a digitally signed document cleared by the Authority. In between, the invoice passes through five technical transformations before it leaves your server, then two after it returns.
The order is not optional. Signing the document must come after it is fully built, because any later edit to the XML invalidates the signature. And computing the hash value depends on the final signed content. That is why we treat the steps as a production line: the output of each step is the input of the next.
The complete submission pipeline diagram
Build UBL 2.1
Sign with CSID certificate
Base64 encode and compute hash
Choose Clearance or Reporting
POST and read the response
Store the approved result
We go through each stage in detail in the following sections. But keep this general principle: the first five stages all run on your server before any contact with the Authority, and the last two depend on the Authority’s response. If any of the first five stages fails, do not send the request at all; fix the document locally first.
Step one: build the invoice document in UBL 2.1 format
Every invoice submitted to the Fatoora platform is an XML document that follows the UBL 2.1 standard (Universal Business Language). This standard defines the tag names, their order, and their data types. The invoice is not sent as JSON nor as free text, but as a structured XML document that the Authority recognizes field by field.
The document divides into three logical layers. The invoice header carries the general identifiers: the invoice number, its type, its date and time, and the currency (SAR). Then the two parties’ data: the seller with its tax number, commercial registration, and address, and the buyer with its data. Then the line items: each line with its quantity, price, and tax rate, followed by the invoice totals before and after tax.
There are two technical elements that are indispensable inside the document, even before signing. The first is the unique identifier (UUID) that you generate for each invoice and that never repeats. The second is the invoice counter (ICV) that increments by one for each invoice on the same device, preserving the chain order. The details of generating these two elements and their exact constraints are explained in the Fatoora platform API endpoints.
The invoice type determines the path later
While building the document, you write the invoice type in the document type tag. This field is not a formal detail, because it later determines which path the invoice takes. The tax invoice (B2B) between two establishments takes the clearance path. And the simplified tax invoice (B2C) for the end consumer takes the reporting path. Write the correct type from the start, because sending an invoice to the wrong path returns it with an error code.
The exact position of each tag and its relation to the stamp later is detailed in The cryptographic stamp technical specification. Here it is enough to come out of this step with a well-formed XML document valid against the UBL 2.1 schema, because the next step will sign it, and it will not accept signing an incomplete document.
Step two: sign with the Cryptographic Stamp Identifier (CSID)
After the document is complete, you sign it digitally. The signature is what turns an ordinary XML file into an approved electronic invoice. For signing, you use the Production CSID, which is a PKI certificate the Authority issued to you for your specific device during the onboarding phase.
Note the naming: the identifier is CSID, short for Cryptographic Stamp Identifier, and not any other arrangement of the letters. Each issuing device has an independent production identifier, so if you run several points of sale or several servers, each has its own certificate, and you must sign each invoice with the certificate of the device that actually issued it.
What does the signature add to the document?
The signing process inserts a signature block inside the document itself, containing the seller’s cryptographic stamp and the certificate fingerprint. It also injects the invoice hash value and the QR (quick response) code built on the invoice data and the signature. These elements are added in specific positions of the UBL tree, and any shift in their position breaks the Authority’s verification.
The golden rule here: do not touch the document after signing. Any edit to any character inside the XML, even a whitespace, invalidates the signature and makes the invoice rejected. If you discover an error in the data after signing, rebuild the document from scratch and sign it anew; do not try to patch the signed one.
| Criterion | Before signing | After signing |
|---|---|---|
| Cryptographic stamp | Not present | Embedded |
| Hash and UUID | Not computed | Computed |
| QR code | Not generated | Generated |
Step three: encode the document in Base64
The Fatoora platform does not receive the XML document as is in the request body. It receives it encoded in Base64 inside a text field. The reason is simple: the request body itself is sent as JSON, and raw XML contains characters that break the JSON structure. So we encode the entire signed document into a single Base64 string and place it in the invoice field inside the JSON body.
This separation between the envelope and the content is intentional. JSON is the transport envelope that the Authority’s server understands. And the XML encoded inside it is the official tax document. The Authority decodes it on its side, reconstructs the original document precisely, then verifies its signature. Any error in the encoding arrives as a corrupted document.
Note that the invoice field in the example above is the entire signed document after encoding it. Encode the document in UTF-8 before converting it to Base64, and do not add line breaks inside the resulting string. The string is one continuous line.
Step four: compute the hash value (invoiceHash)
The hash value is a digital fingerprint of the document, computed with a hashing algorithm on the signed content, then itself encoded in Base64. The Authority uses it to verify that the document that reached it is the same one you signed, without a single character changed along the way.
The value is computed on the document after signing, not before, and on the canonical content of the XML per the rules the specification defines. This order is essential: if you compute the hash before signing, it will differ from the value the Authority computes, so it rejects the invoice for a hash mismatch.
The hash links invoices in a chain
The hash value does not serve only the current invoice, but also enters into the next invoice. Each invoice carries the hash of the previous invoice on the same device, forming an interlinked chain from which you cannot delete a link or reorder it without breaking the chain. The first invoice on the device carries an initial hash value agreed upon in the specification. Store each invoice’s hash as soon as it is issued, because you will need it as an input for the invoice that immediately follows it.
Step five: choose the path, clearance or reporting
Now you have a signed, encoded document and a hash value. The step before submission is choosing the request destination. And here the fundamental difference appears between two paths the Authority handles completely differently.
The clearance path is for the tax invoice between establishments (B2B). In this path, you send the invoice to the Authority before you hand it to the buyer. The Authority clears it with its own stamp and returns to you a cleared copy. This returned copy, not the one you sent, is what you hand to the buyer. That is, clearance is instantaneous and precedes delivery.
The reporting path is for the simplified tax invoice to the consumer (B2C). Here you hand the invoice to the buyer immediately at the point of sale, then report it to the Authority within twenty-four hours. The Authority does not clear it beforehand, but receives a notification of it for archiving and reconciliation. That is, delivery precedes reporting.
| Criterion | Clearance (B2B) | Reporting (B2C) |
|---|---|---|
| Timing | Before delivery | Within 24 hours |
| Clearance-Status | 1 | 0 |
| Type | Standard | Simplified |
At the request level, the two paths differ in the endpoint you send to, and in a header that distinguishes between them, carrying a value that differs between the paths. The full detail of each path, with its endpoint, its headers, and its response shape, is detailed in two separate guides we will publish for each path on its own. Here it is enough to choose the correct path based on the invoice type you set in step one.
Step six: send the request (POST)
You are now ready to submit. The request is of type POST, and its body is JSON containing the three fields: the hash value, the unique identifier, and the Base64-encoded document. The headers, however, carry the request’s own authentication data (not the invoice’s cryptographic stamp), and are built on the production identifier and its associated secret.
The request authentication headers differ from the invoice signature. The signature inside the document proves who issued the invoice. And the request authentication header proves to the Authority that whoever knocks on the endpoint is truly the owner of the registered device. The details of building these headers are explained in API integration with the Fatoora platform.
Set the connection timeout and do not retry needlessly
Set a reasonable timeout for the request, because some responses are delayed under load. But do not resend the same invoice merely because the response is late, because the unique identifier and the invoice counter make each invoice unique, and random resending may confuse the hash chain. Handle retries carefully, and distinguish between a transient network error that warrants a retry, and a logical rejection from the Authority that requires fixing the document rather than resending it as is.
Step seven: parse the response and store the result
The return of the response does not mean the process is over, but its beginning from the storage side. The first thing you read is the status code. Full success means the Authority accepted the invoice. Success with warnings means it was accepted but with notes you must record and address in the future. Rejection, however, means it was not accepted, and it may not be considered a valid invoice.
The difference in what you store follows the path. In the clearance path, the response contains the copy cleared by the Authority in a dedicated field. This copy is the official invoice that you store and hand to the buyer, not the one you sent. In the reporting path, the response confirms receipt of the report, and the copy you handed to the buyer at the point of sale remains the official copy, with the reporting confirmation stored alongside it.
What do you store with each invoice?
For each invoice, keep a complete, later-auditable bundle: the unique identifier, the invoice counter, the hash value, the signed document, the cleared copy if any, the status code, and any warnings the Authority returned. This bundle is your reference at any audit, and it is what proves the invoice went through the correct path and reached the Authority.
The status codes and error types that may come back to you, and how to distinguish an error that warrants a retry from one that requires a fix, are detailed in Fatoora platform API endpoints. Always store the full error text, because it is the fastest way to diagnose the cause of a rejection.
Make sure the invoice is not sent twice
One of the most confusing things for submission systems is sending the same invoice twice when the response is delayed. The user presses the submit button again, or the system retries automatically, while the first invoice may have already reached the Authority. The result is two invoices with different identifiers for the same sale, which is both an accounting and a tax defect.
Protection from this starts at the storage layer. Bind each invoice in your system to a clear status: submitting, submitted successfully, or rejected. Do not start a new submission for an invoice still in the “submitting” status until its fate is decided. And if the response is cut off, query the invoice’s status by its unique identifier before you decide to resend. The unique identifier and the invoice counter are your tools to verify whether a particular invoice was sent or not.
A quick reference table for the seven submission steps
The following table summarizes the entire submission pipeline in one row per step: what you do in it, what its input is, and what its output is that becomes the input of the next step. Keep it as a quick reference while building the pipeline.
| Step | What you do | Output |
|---|---|---|
| 1. Build UBL | You compose the XML document per UBL 2.1 with UUID and ICV | A well-formed XML document |
| 2. Signing | You sign the document with the production identifier (CSID) | A document signed with a cryptographic stamp and QR |
| 3. Encoding | You encode the signed document in Base64 | A Base64 string for the invoice |
| 4. Hash | You compute the hash value on the canonical content | value invoiceHash |
| 5. Choose the path | You choose clearance (B2B) or reporting (B2C) | The appropriate endpoint and header |
| 6. Submission | You send a POST request with a JSON body |
A response from the Authority |
| 7. Storage | You read the status code and store the result | A cleared or reported invoice stored |
Common errors in the submission pipeline
Most rejection cases in submission come down to a limited number of recurring causes. The first is editing the document after signing, even by a single space, which invalidates the signature. The second is computing the hash value on a non-canonical version of the XML, so it does not match what the Authority computes.
The third is breaking the hash chain, where the invoice carries the hash of a wrong invoice instead of its immediate predecessor. The fourth is sending the invoice to the wrong path, such as sending a B2B invoice to the reporting path. The fifth is errors in the format of the identifiers themselves, such as the tax number and commercial registration, which are among the most common causes of the Authority’s warnings.
The practical rule: fix each error in its own layer. A document-structure error is fixed in step one. A signature error in the second. A hash error in the fourth. A path error in the fifth. Do not try to fix an error in one layer by resending the request from another layer.
Distinguishing the transient error from the logical rejection
The most important distinction in error handling is between two completely different types. The transient error is a problem in transport, not in content: a network drop, an expired timeout, or a momentary load on the server. This type warrants a later retry with the exact same document, because the document is sound and did not arrive at all.
The logical rejection, however, is a problem in the document itself: an invalid signature, a mismatched hash, a missing field, or an identifier in a wrong format. This type you never retry with the same document, because the result will repeat. Fix the root cause, rebuild the document and sign it anew, then send a new document with a new unique identifier.
Make your system’s retry logic distinguish between the two types automatically based on the status code and the error text. Blind retrying on a logical rejection wastes time and may confuse your records. And ignoring the transient error loses you invoices that never reached the Authority at all. Log every attempt with its time and result so you can track which invoice got stuck and why.
How Qoyod helps you submit the invoice via API
You may build this submission pipeline yourself step by step, or rely on an accounting system that handles it entirely on your behalf. Qoyod is an accounting software compliant with Phase Two of e-invoicing, and it manages the entire seven-step submission chain without you writing a single line of code.
- Building UBL 2.1 ready for submission: Qoyod builds the invoice document in the required UBL format, and generates the unique identifier (UUID) and the invoice counter (ICV) for each invoice, so you do not compose XML manually.
- Signing with the Cryptographic Stamp Identifier automatically: Qoyod manages the production identifier (CSID) for each device, signs each invoice with it, and injects the QR code and the signature block in their correct position.
- Computing the hash value and preserving the chain: Qoyod computes the hash value (invoiceHash) on the signed document, and links each invoice to its predecessor’s hash, so it keeps the invoice chain intact without a break.
- Routing the correct path automatically: Qoyod routes the tax invoice to the clearance (Clearance) path and the simplified invoice to the reporting (Reporting) path according to its type, so you never send an invoice to the wrong path.
- Storing the cleared copy and the reporting confirmation: Qoyod stores the copy returned from the Authority in the clearance path, and the reporting confirmation in the reporting path, along with the status code and any warnings, in an auditable log for any inspection.
Important note: Qoyod transmits what you issue to the Authority, and the Authority is the party that verifies the invoice and clears it. Qoyod does not filter tax business-rule errors on the Authority’s behalf before the invoice reaches it.
Frequently asked questions about invoice submission via API
Do I send the XML document directly in the request body?
No. You send the signed document encoded in Base64 inside the invoice field in the JSON body. JSON is the transport envelope, and XML is the tax document inside it.
When do I compute the hash value, before or after signing?
After signing, and on the canonical content of the document. If you compute it before signing, it will differ from the Authority’s value, so the invoice is rejected for a hash mismatch.
What is the difference between clearance and reporting in submission?
Clearance (Clearance) is for the B2B tax invoice: you send it before delivering to the buyer, and the Authority returns a cleared copy. Reporting (Reporting) is for the B2C simplified invoice: you hand it to the buyer immediately, then report it to the Authority within 24 hours.
Which copy do I hand to the buyer in the clearance path?
The copy returned from the Authority after clearance, which is the one that carries the Authority’s stamp, and not the copy you sent.
I edited the invoice data after signing, what do I do?
Rebuild the document from scratch and sign it anew. Any edit after signing, even a single space, invalidates the signature and makes the invoice rejected.
Do I need to build this submission pipeline myself?
No. An accounting system compliant with Phase Two, such as Qoyod, manages the entire chain on your behalf, from building UBL to signing, hashing, path routing, and storing the result.
Submit your invoices to the Fatoora platform without writing a single line of code
Qoyod manages the entire submission chain: building UBL, signing with the Cryptographic Stamp Identifier, computing the hash, routing the path, and storing the cleared copy. Try Phase Two-compliant invoicing straight from your account.