Skip to main content
What the provider returns does not stay in the synchronous response. It is attached to the order and travels in all of its events, so every field of the response has a final consumer that is not FIRE. This page exists to close that circle: if you are implementing the endpoint, here you see what happens with what you return.

The journey

The point of sale numbers before the order exists: it charges, requests the numbers, prints, and only afterwards injects the sale. On injection, FIRE looks up the numbering for that orderCode and attaches it to the order. From there it travels in data.fiscalRepresentation of order.opened, order.completed, order.invoiced, order.cancelled and order.reversed.
The event amounts are not on the same scale as those in the numbering request.And it is not a marginal detail: the event is where the sale you issue comes from. The numbering gives you the identifiers; the amounts, the lines and the buyer you declare to the authority you take from here. That is why this is the place where the scale can bite.Everything monetary in data.payments travels as an integer in a string, multiplied by 10,000 — it is the scale FIRE stores with, to do arithmetic with integers and not drag floating-point error when summing taxes.An example with Colombia — each country travels in its own currency, but the scale is the same:Divide by 10,000 every amount you take from the event before declaring it to the authority. For the CUFE hash use the ones from the numbering request, which are the same values and already come unscaled.It is not an inconsistency in the data —it is the same amount in two conventions— but finding out late is expensive: if you do not divide, you declare 500,000,000 COP for a sale of 50,000 COP —ten thousand times the amount—, the document is well formed and the authority accepts it.

Field by field

Ecuador, which is the document block of /fiscal/ec/prekeys: A complete example, with real data:
The block above always has the same keys, with null for those that do not apply — that is what you program against. What changes per country lives inside countryData, and there only the keys of the country that numbered travel: an Ecuadorian receipt does not carry cufe, nor a Colombian one claveAcceso.

The four cases, in full

These are all the states the block can have, and which response of yours produces them. The block above always brings the same 12 keys: what changes are the values and the contents of countryData.
You returned 2xx with document.
The integrator prints and reconciles. countryData brings the SRI’s vocabulary and nothing from other countries.
You returned non-2xx with retryable: false.
It is a charged sale with no fiscal receipt. The integrator compensates on its side and returns the receipt via the callback. Retrying does not fix it: the data has to be corrected.
You returned non-2xx with retryable: true.
Same block as the previous one; what changes is the numberingStatus and the scope. There is no receipt, but there may be one: the channel retries with the same orderCode.
There was a timeout or the connection dropped. You never produce this state: stating it would imply having answered.
It is the most delicate state, and the one most easily misread. It does not mean “there is no receipt”: it means we do not know. You may have numbered, consumed a sequential and issued the document, and the response got lost on the way back.An integrator that reads it as “there is no receipt” and compensates by issuing another one declares the same sale twice before the authority. With FAILED_RETRYABLE that compensation is correct; with PENDING it is an expensive mistake.That is why your deduplication has to be by orderCode: the retry arrives with the same orderCode and you return the same document with reused: true, instead of numbering another one. Do not expect an idempotency header — we do not send you one.

And the case with no numbering

When the sale did not go through any provider —the merchant does not invoice, or the country has no fiscal gateway— the whole block travels as null:
The key is never omitted. The integrator branches by value:
documentType today is always SALE_INVOICE in the order’s events. CREDIT_NOTE exists in the contract —it is produced by operation: "CANCEL"— but the numbering of the cancellation is not attached to the order yet. When it is enabled, it is the same block with documentType: "CREDIT_NOTE".

Three fields worth understanding well

provider and metadata — two blocks, two destinations

They look alike and are not the same thing, so they travel separately: Neither of the two carries providerCode: that is our adapter identifier and it already travels separately, above the block.
provider.reference is the only reason this block has shape. When something goes wrong, it is what the integrator quotes to you so you can find the operation in your records. Buried in an opaque bag —which by contract nobody should program against— it did not fulfil that function.
What you put in metadata reaches the integrator untouched, in providerMetadata. We do not interpret it, we do not validate it, we do not rename it. That has two sides:
It is the only field of the response we do not control. If you put something sensitive in there —a credential, an internal identifier of your infrastructure, another customer’s data— you are publishing it to the point of sale’s integrator.
And on the other side: nobody should program against its keys. It is declared opaque precisely so you can change it without breaking anyone. If a piece of data is important enough for the integrator to branch on it, it does not belong in metadata — it belongs in the contract.
Do not repeat inside it what already has its place. Sending failure inside metadata, or the name from the neighbouring block, stores the same fact twice — and two copies drift apart. And do not change the bag between an issuance and its idempotent retry: whoever reads the event will see that something “changed” that did not change.

graphic — it is the only thing that cannot be derived

Everything else in the response can be reconstructed or composed. graphic cannot: the Brazilian NFC-e QR is a URL signed with a hash that only the issuer can build. If it does not arrive, the receipt prints without a QR. It travels as is, untransformed: FIRE passes it on to the point of sale, which renders it with its own library. We do not generate images on this side — size and resolution depend on the printer, and only whoever prints knows that.

failure — it enables compensation at the other end

When numbering fails, the error does not stay in a log: it travels in the event. The sale was charged anyway and the integrator needs to know it ended up without a fiscal receipt. With that it can compensate on its side and return the receipt via the callback. Without it, a charged sale with no receipt is indistinguishable from an account that does not invoice. That is why failure.code has to be stable and failure.message actionable: they are not read only by our support team, they are read by the end customer’s system.

What does NOT travel to the events

And the authority’s verdict, separately

fiscalRepresentation is the numbers that were printed, and they never change. Their existence does not mean the authority authorized the receipt. The verdict arrives later via your callback and travels elsewhere in the event:
They are two distinct lifecycles and the contract keeps them separate on purpose: one is immutable and the other gets updated.