Skip to main content
POST
/
api
/
v1
/
webhooks
/
fiscal
/
callback
Fiscal callback
curl --request POST \
  --url https://app.fire.rest/api/v1/webhooks/fiscal/callback \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <x-api-key>' \
  --data '
{
  "countryCode": "<string>",
  "eventType": "<string>",
  "providerEventId": "<string>",
  "occurredAt": "<string>",
  "orderId": "<string>",
  "eventId": "<string>",
  "document": {},
  "error": {
    "code": "<string>",
    "message": "<string>"
  },
  "providerSpecific": {}
}
'
{
  "received": true,
  "duplicate": false,
  "eventId": "1686fca1-0a26-4a89-b2f2-fe93b15a4434",
  "webhookEventId": "6d950243-6a0c-415c-b547-bddf9af8ad61",
  "status": "queued",
  "firstReceivedAt": "2026-06-11T15:00:34.729Z",
  "message": "Event accepted and queued for processing."
}
This endpoint is inbound — your fiscal provider POSTs to it whenever a fiscal document is authorized, rejected, denied, cancelled, or errors out. It is the canonical, multi-country fiscal callback API that accepts payloads from BR, CO, EC, CL, AR, VE. Fire validates the payload, updates the order’s fiscal state in fiscal_documents and orders.fiscal, and — for Brazil — dispatches downstream order.invoiced / order.reversed events to your Integration Flows.
This endpoint is asynchronous. Fire authenticates, runs the source-of-truth + tenant guard, deduplicates, pre-marks the order as processing, and enqueues the callback — then returns 202 Accepted with a webhookEventId (typically in under 100 ms). The fiscal_documents / orders.fiscal update happens a moment later in a background worker (usually within ~2 seconds). To check the processing outcome, poll GET /v1/webhooks/events/{webhookEventId}. Client-fixable problems (bad payload, wrong eventId, wrong tenant, action reusing another event’s id) are rejected synchronously with 4xx before the 202.

How it complements order.completed and order.cancelled

order.completed and order.cancelled carry an order’s business state. The fiscal callback carries the fiscal state (SEFAZ / DIAN / SRI / SII / AFIP / SENIAT authorization). Together they form the lifecycle below: After the callback is processed, the next order.completed or order.cancelled event for the same orderId reflects the updated fiscal state in:
  • data.store.storeFiscalConfig — emitter context (CNPJ / NIT / RUC / RUT / CUIT / RIF…)
  • data.payments.metadata.fiscal — aggregate fiscal totals (BR populated; other countries null)
  • data.orderLines[].metadata.fiscal — per-line fiscal classification (BR populated; others null)
For Brazil specifically, a separate order.invoiced (or order.reversed) event is also emitted with the SEFAZ document references in data.fiscal.
Today, only order.invoiced and order.reversed are wired as outbound flow events (Brazil). The endpoint validates and stores callbacks for all 6 countries (CO, EC, CL, AR, VE, BR), and the updated fiscal state shows up in the next order.completed / order.cancelled event regardless of country. The corresponding outbound flow events for CO/EC/CL/AR/VE are coming.

Authentication

This endpoint requires an API key with the webhooks:fiscal scope, vendor-scoped to the account that owns the order. Unscoped or account-only keys are rejected with 403 Forbidden.
x-api-key
string
required
Your Fire API key. Generate one from the dashboard at Settings → API Keys → Developer keys, with scope webhooks:fiscal and a vendor binding for the account whose orders this key may update.
Authorization
string
Optional Bearer <token>. Not currently required for this endpoint, but reserved for future provider-issued tokens.

Request body

The body is a JSON object with these top-level fields. The document shape is discriminated by countryCode — see Document by country below.
countryCode
string
required
ISO 3166-1 alpha-2 country code. One of BR, CO, EC, CL, AR, VE. Determines the validation rules for the document object.
eventType
string
required
Document state. One of:
  • authorized — the fiscal authority approved the document
  • cancelled — a previously authorized document was cancelled
  • rejected — the document was rejected (validation, schema, signature)
  • denied — the authority denied the request (typically a permanent business rule failure)
  • error — a non-recoverable error occurred at the provider or authority
When eventType is authorized or cancelled, the document field is required. When it is rejected, denied, or error, the error field is required instead.
providerEventId
string
required
Your provider’s own id for this delivery. Stored for audit/forensics — it is not the idempotency key. Fire deduplicates on (orderId, eventId), so you may safely send a fresh providerEventId on every retry without creating duplicates. Use the provider’s native event id if available; otherwise a UUID.
occurredAt
string
required
ISO 8601 UTC timestamp of when the event happened at the fiscal authority (not when the provider sent the callback).
orderId
string
required
UUID of the order in Fire. Matches data.orderId in order.completed and order.cancelled events. Fire uses this to find the existing fiscal document.
eventId
string
required
Correlation UUID and idempotency key (together with orderId). It must be the event.id of a V4 envelope Fire emitted for this order — echo it exactly; never invent it.
  • Source-of-truth check. An eventId that does not reference a Fire event for that order is rejected with 400 before the 202.
  • Each action carries its own event. Echo the event.id of the event that drove this action — the order.invoiced emission for an authorized callback, the cancellation/reversal event for a cancelled callback. Reusing one action’s eventId for a different eventType (e.g. a cancelled that echoes the authorized’s eventId) is rejected with 409 — a cancellation must reference its own event, not ride on the authorization’s. See Idempotency & scenarios.
document
object
The authorized / cancelled fiscal document. Required when eventType is authorized or cancelled. Shape varies per country — see Document by country.
error
object
Error context for negative outcomes. Required when eventType is rejected, denied, or error.
providerSpecific
object
Free-form bag for provider-specific extras (raw response, internal IDs, etc.). Not validated; passed through to the audit log.

Document by country

The document field’s required shape depends on countryCode. All variants share the base fields below (common to all 6 countries) plus the country-specific identifiers required by that authority.

Common base fields

These apply to any countryCode. docType and docSubtype are required; the rest are optional — send them whenever your fiscal provider has them available. Fire persists them in fiscal_documents / orders.fiscal and re-emits them in the outbound order.invoiced / order.reversed events (Brazil) and in the next order.completed / order.cancelled.
FieldTypeRequiredNotes
docType"invoice" / "cancellation"Document class — invoice for issuance, cancellation for a cancellation
docSubtypestringCountry/provider subtype (e.g. nfce, nfe, factura, boleta, factura_a)
pdfUrlstring (URL) | nullDownload link for the document PDF (DANFE / graphical representation)
xmlUrlstring (URL) | nullDownload link for the canonical XML from the fiscal authority
emittedAtstring (ISO 8601) | nullUTC timestamp when the authority authorized/emitted the document
cancelledAtstring (ISO 8601) | nullUTC timestamp of the cancellation — relevant when eventType is cancelled
totalAmountnumber | nullDocument gross total
taxAmountnumber | nullTotal tax amount
currencyCodestring | nullISO 4217 currency code — exactly 3 letters (e.g. BRL, COP, USD)
pdfUrl and xmlUrl are stored exactly as you send them — Fire does not download or re-host the file. If your provider signs these URLs with expiry, note that the stored link may expire; download and persist the artifact on your side if you need durable access.
Brazilian fiscal documents (NF-e / NFC-e). Use this country code when sending callbacks from any BR fiscal provider.In addition to the common base fields (docType, docSubtype, pdfUrl, xmlUrl, emittedAt, etc.), BR requires these specific identifiers:
FieldTypeRequiredNotes
chaveAcessostringExactly 44 digits — SEFAZ access key
protocolostringSEFAZ authorization protocol
numeroint / stringDocument number
serieint / string | nullDocument series (encoded in chave)
modelo55 / 65 | null55 = NF-e, 65 = NFC-e
cnpjEmitentestring | nullEmitter CNPJ
The example below includes the optional base fields (pdfUrl, xmlUrl, emittedAt, totalAmount, taxAmount, currencyCode) — all may be omitted, but send them if your provider has them:
{
  "countryCode": "BR",
  "eventType": "authorized",
  "providerEventId": "evt-br-1",
  "occurredAt": "2026-04-26T14:32:11.000Z",
  "orderId": "550e8400-e29b-41d4-a716-446655440000",
  "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "document": {
    "docType": "invoice",
    "docSubtype": "nfce",
    "chaveAcesso": "35260229062609000177650500000000011000000010",
    "protocolo": "141210001176277",
    "numero": 1,
    "serie": 50,
    "modelo": 65,
    "cnpjEmitente": "29062609000177",
    "emittedAt": "2026-04-26T14:32:10.000Z",
    "totalAmount": 142.90,
    "taxAmount": 18.57,
    "currencyCode": "BRL",
    "pdfUrl": "https://api.fiscal-provider.example/nfce/69fa97fe427d1240856e1282/pdf",
    "xmlUrl": "https://api.fiscal-provider.example/nfce/69fa97fe427d1240856e1282/xml"
  }
}

Negative outcomes (rejected, denied, error)

For non-authorized states, omit document and provide error. The countryCode still applies (validates routing); the document itself is not required because there is no authorized artifact.
Rejected
{
  "countryCode": "CO",
  "eventType": "rejected",
  "providerEventId": "evt-co-2",
  "occurredAt": "2026-04-26T14:32:11.000Z",
  "orderId": "550e8400-e29b-41d4-a716-446655440000",
  "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "document": null,
  "error": {
    "code": "DIAN_42",
    "message": "CUFE inválido"
  }
}

Response

On success the endpoint returns 202 Accepted — the event was authenticated, validated, deduplicated, and enqueued. A 202 does not mean the fiscal document was updated yet; that happens asynchronously in a background worker. Use the status endpoint to confirm the final outcome. The body carries two distinct ids so there is no ambiguity: eventId is the id you sent (echoed back), webhookEventId is Fire’s id for the queued record.
received
boolean
Always true when the request was accepted and enqueued.
duplicate
boolean
true when this (orderId, eventId) was already ingested with the same eventType — the existing record is returned and nothing is re-enqueued. false for a fresh event.
eventId
string
Echo of the eventId you sent (the Fire emission’s event.id). Use it to correlate this acknowledgement with your request.
webhookEventId
string
Fire’s id for the queued record in webhook_events. Pass it to GET /v1/webhooks/events/{webhookEventId} to poll the processing outcome. On a duplicate it is the same id returned the first time.
status
string
Current queue status of the record — queuedprocessingprocessed (and retry / failed / dead / ignored).
firstReceivedAt
string
ISO 8601 UTC timestamp of when Fire first received this event. Stable across retries — useful as a trace anchor.
message
string
Human-readable summary of what happened.
{
  "received": true,
  "duplicate": false,
  "eventId": "1686fca1-0a26-4a89-b2f2-fe93b15a4434",
  "webhookEventId": "6d950243-6a0c-415c-b547-bddf9af8ad61",
  "status": "queued",
  "firstReceivedAt": "2026-06-11T15:00:34.729Z",
  "message": "Event accepted and queued for processing."
}

Checking the outcome

Because processing is asynchronous, the 202 only confirms the event was queued. To see whether the fiscal document was actually updated, poll the companion status endpoint with the webhookEventId returned by the 202:
GET https://app.fire.rest/api/v1/webhooks/events/{webhookEventId}
x-api-key: <your webhooks:fiscal key>
status
string
Queue lifecycle: queuedprocessingprocessed (done) · failed / dead (gave up after retries) · retry (waiting for the next attempt) · ignored.
attempts
number
Processing attempts so far.
result
object | null
On success, the worker outcome — e.g. { "kind": "updated", "documentId": "…", "receiptId": "…" }.
error
object | null
{ "message": "…" } when the last attempt failed; null otherwise.
{
  "id": "9e6c8af8-af80-4967-9422-c096ab43c0e7",
  "source": "fiscal_generic",
  "eventType": "authorized",
  "status": "processed",
  "attempts": 1,
  "processedAt": "2026-04-26T14:32:13.000Z",
  "result": { "kind": "updated", "documentId": "1a2b3c4d-…", "receiptId": "9b2a3c4d-…" },
  "error": null
}
A 404 is returned for unknown ids — or ids that belong to another tenant — with no existence leak. Authenticate with the same webhooks:fiscal key you used for the callback.

Idempotency & scenarios

Fire deduplicates callbacks on (orderId, eventId)not on providerEventId (which you may regenerate freely). An event is unique to its order: the same (orderId, eventId) resent with the same eventType is a benign replay; the same (orderId, eventId) with a different eventType is an attempt to ride one action on another’s event, which Fire rejects. This is the full behavior matrix — every combination you can send:
ScenarioResult
New (orderId, eventId)202 · duplicate: false — enqueued
Same (orderId, eventId, eventType) resent (any providerEventId)202 · duplicate: true — existing record returned, not re-enqueued
Same (orderId, eventId) but different eventType (e.g. cancelled echoing the authorized’s eventId)409 — rejected. Use the event that belongs to this action
eventId not emitted by Fire for this orderId400 — wrong/invented eventId
Order/event not under your API key’s account + vendor403
Malformed payload (Zod), missing required fields400
Missing / invalid API key401
Auth store momentarily unreachable503 — transient, retry
A cancellation needs its own event. You cannot cancel a document by resending the authorization’s eventId with eventType: "cancelled" — that returns 409. Fire is the source of truth: a cancellation must reference the cancellation/reversal event Fire emitted (a distinct eventId). Returning a 202 duplicate here would wrongly tell you the cancellation was accepted while Fire never cancelled — so Fire rejects it explicitly instead.
409 vs 202. A 409 is the only case where a well-formed, authenticated callback is refused at the idempotency layer — because accepting it would diverge state. A same-type replay is never an error: it returns 202 so your retries stay clean. A 503 is ours (transient infra), so it is safe and expected to retry.

Concurrency

Status transitions on fiscal_documents use optimistic locking. If two callbacks for the same document arrive concurrently, one succeeds and the other resolves to idempotent or regression depending on the order. The orders.fiscal JSONB merge is atomic.

What happens after the 202

The 202 only enqueues the event. A background worker then picks it up — within ~2 seconds via the instant wake-up, or on the next poll cycle as a fallback — and:
  1. fiscal_documents row is upserted with the new status and document references (chaveAcesso, protocolo, cufe, cae, etc., per country).
  2. orders.fiscal JSONB column is merged with the same data — making it visible in the next order.completed / order.cancelled event for the same orderId, regardless of country.
  3. An outbound flow trigger is dispatched — for Brazil, triggerType = 'order.invoiced' (for eventType=authorized) or 'order.reversed' (for eventType=cancelled). Active Integration Flows for that trigger run and POST to your endpoint.
  • Today, only order.invoiced and order.reversed are wired. They fire when an authorized/cancelled callback for a BR store is processed.
  • For CO/EC/CL/AR/VE, the equivalent outbound events are not yet wired — callbacks are validated and stored, and the fiscal state is reflected in the next order.completed / order.cancelled event for the same order.

order.completed

See where the fiscal state lands inside the V4 order snapshot.

order.invoiced

Brazil-only outbound event triggered by this callback.

Inject order

The order injection endpoint that creates the order this callback updates.

Authentication

How API keys, scopes, and vendor binding work.