> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fire.rest/llms.txt
> Use this file to discover all available pages before exploring further.

# Fiscal callback

> Inbound endpoint your fiscal provider POSTs to when a fiscal document changes state. Multi-country (BR, CO, EC, CL, AR, VE). Updates the order's fiscal state inside Fire and — for Brazil — triggers downstream order events.

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.

<Note>
  **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}`](#checking-the-outcome). Client-fixable problems (bad payload, wrong `eventId`, wrong tenant, action reusing another event's id) are rejected **synchronously** with `4xx` **before** the `202`.
</Note>

## 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:

```mermaid theme={null}
flowchart LR
    A([Order paid]) --> B([order.completed])
    B --> C[Your fiscal provider<br/>emits the document]
    C --> D([POST /v1/webhooks/fiscal/callback])
    D --> E[Fire updates fiscal_documents<br/>+ orders.fiscal JSONB]
    E -. BR only .-> F([order.invoiced event])
```

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`.

<Note>
  **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.
</Note>

## 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`.

<ParamField header="x-api-key" type="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.
</ParamField>

<ParamField header="Authorization" type="string">
  Optional `Bearer <token>`. Not currently required for this endpoint, but reserved for future provider-issued tokens.
</ParamField>

## Request body

The body is a JSON object with these top-level fields. The `document` shape is **discriminated by `countryCode`** — see [Document by country](#document-by-country) below.

<ParamField body="countryCode" type="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.
</ParamField>

<ParamField body="eventType" type="string" required>
  Document state. One of:

  * `fiscal_graphic` — the provider is delivering the **representación gráfica** (the printable artifact) for the document. **It is not an approval** and not a terminal state — see [The `fiscal_graphic` state](#the-fiscal_graphic-state) below.
  * `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 `fiscal_graphic`, `authorized` or `cancelled`, the `document` field is **required**. When it is `rejected`, `denied`, or `error`, the `error` field is **required** instead.
</ParamField>

<ParamField body="providerEventId" type="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.
</ParamField>

<ParamField body="occurredAt" type="string" required>
  ISO 8601 UTC timestamp of when the event happened at the fiscal authority (not when the provider sent the callback).
</ParamField>

<ParamField body="orderId" type="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.
</ParamField>

<ParamField body="eventId" type="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 terminal `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](#idempotency--scenarios).
</ParamField>

<ParamField body="document" type="object">
  The authorized / cancelled fiscal document. **Required** when `eventType` is `fiscal_graphic`, `authorized` or `cancelled`. Shape varies per country — see [Document by country](#document-by-country).
</ParamField>

<ParamField body="error" type="object">
  Error context for negative outcomes. **Required** when `eventType` is `rejected`, `denied`, or `error`.

  <Expandable title="error">
    <ParamField body="code" type="string">
      Optional provider/authority error code (e.g. `DIAN_42`, `cStat_204`).
    </ParamField>

    <ParamField body="message" type="string" required>
      Human-readable message. Min 1 character.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="providerSpecific" type="object">
  Free-form bag for provider-specific extras (raw response, internal IDs, etc.). Not validated; passed through to the audit log.
</ParamField>

## 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`.

| Field          | Type                           | Required | Notes                                                                           |
| -------------- | ------------------------------ | -------- | ------------------------------------------------------------------------------- |
| `docType`      | `"invoice"` / `"cancellation"` | ✓        | Document class — `invoice` for issuance, `cancellation` for a cancellation      |
| `docSubtype`   | string                         | ✓        | Country/provider subtype (e.g. `nfce`, `nfe`, `factura`, `boleta`, `factura_a`) |
| `pdfUrl`       | string (URL) \| null           |          | Download link for the document PDF (DANFE / graphical representation)           |
| `xmlUrl`       | string (URL) \| null           |          | Download link for the canonical XML from the fiscal authority                   |
| `emittedAt`    | string (ISO 8601) \| null      |          | UTC timestamp when the authority authorized/emitted the document                |
| `cancelledAt`  | string (ISO 8601) \| null      |          | UTC timestamp of the cancellation — relevant when `eventType` is `cancelled`    |
| `totalAmount`  | number \| null                 |          | Document gross total                                                            |
| `taxAmount`    | number \| null                 |          | Total tax amount                                                                |
| `currencyCode` | string \| null                 |          | ISO 4217 currency code — exactly 3 letters (e.g. `BRL`, `COP`, `USD`)           |

<Note>
  `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.
</Note>

<Tabs>
  <Tab title="Brazil (BR)">
    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](#common-base-fields) (`docType`, `docSubtype`, `pdfUrl`, `xmlUrl`, `emittedAt`, etc.), BR requires these specific identifiers:

    | Field          | Type                 | Required | Notes                                |
    | -------------- | -------------------- | -------- | ------------------------------------ |
    | `chaveAcesso`  | string               | ✓        | Exactly 44 digits — SEFAZ access key |
    | `protocolo`    | string               | ✓        | SEFAZ authorization protocol         |
    | `numero`       | int / string         | ✓        | Document number                      |
    | `serie`        | int / string \| null |          | Document series (encoded in chave)   |
    | `modelo`       | `55` / `65` \| null  |          | `55` = NF-e, `65` = NFC-e            |
    | `cnpjEmitente` | string \| null       |          | Emitter 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:

    ```json theme={null}
    {
      "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"
      }
    }
    ```
  </Tab>

  <Tab title="Colombia (CO)">
    Colombian fiscal documents (DIAN).

    | Field               | Type                   | Required | Notes                                                             |
    | ------------------- | ---------------------- | -------- | ----------------------------------------------------------------- |
    | `cufe`              | string                 | ✓        | DIAN unique fiscal code                                           |
    | `prefijo`           | string                 | ✓        | Document prefix                                                   |
    | `numeroDian`        | string                 | ✓        | DIAN document number                                              |
    | `qrCode`            | string (URL) \| null   |          | QR for the printed document                                       |
    | `numeroComprobante` | string \| null         |          | The **visible** number, already assembled: `prefix + consecutive` |
    | `ambiente`          | `"1"` \| `"2"` \| null |          | **`1` production · `2` testing** — the opposite of the SRI        |

    ```json theme={null}
    {
      "countryCode": "CO",
      "eventType": "authorized",
      "providerEventId": "evt-co-1",
      "occurredAt": "2026-04-26T14:32:11.000Z",
      "orderId": "550e8400-e29b-41d4-a716-446655440000",
      "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "document": {
        "docType": "invoice",
        "docSubtype": "factura",
        "cufe": "633732c7a2a577bfa1828551e64d03f715f0",
        "prefijo": "C012",
        "numeroDian": "001",
        "numeroComprobante": "C012001",
        "ambiente": "1",
        "qrCode": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=633732c7a2a577bfa1828551e64d03f715f0",
        "emittedAt": "2026-04-26T14:32:10.000Z",
        "totalAmount": 95000.00,
        "taxAmount": 15170.17,
        "currencyCode": "COP",
        "pdfUrl": "https://api.fiscal-provider.example/dian/C012001/pdf",
        "xmlUrl": "https://api.fiscal-provider.example/dian/C012001/xml"
      }
    }
    ```

    <Note>
      **The names are the same as in the numbering**, deliberately: `cufe`, `prefijo`,
      `numeroDian`, `numeroComprobante`, `qrCode` and `ambiente` mean exactly the same here as
      in the prekey response. It is the same document told twice, and if the names diverged,
      reconciling both paths would stop being a matter of comparing fields.

      **`numeroDian` goes without the prefix** (`990000001`, not `SETP990000001`). The assembled
      number is `numeroComprobante`. Sending the assembled one in both makes reconciliation
      compare `SETP990000001` against `990000001`, and they never match.

      Both new fields are **optional**: whoever already sends callbacks without them keeps
      working. The callback is never rejected over a missing field — by the time it arrives, the
      document already exists before the DIAN.
    </Note>
  </Tab>

  <Tab title="Ecuador (EC)">
    Ecuadorian fiscal documents (SRI).

    | Field                | Type                  | Required | Notes                                                                |
    | -------------------- | --------------------- | -------- | -------------------------------------------------------------------- |
    | `claveAcceso`        | string                | ✓        | Exactly 49 digits — SRI access key                                   |
    | `numeroAutorizacion` | string                | ✓        | SRI authorization number                                             |
    | `numeroComprobante`  | string \| null        |          | The **visible** number, already assembled: `estab-ptoEmi-secuencial` |
    | `ambiente`           | `'1'` / `'2'` \| null |          | `1` = test, `2` = production                                         |

    <Note>
      **`numeroComprobante` is not `numeroAutorizacion`.** The first one is the number printed
      on the document —fifteen digits in three segments, art. 18 of the Reglamento de
      Comprobantes de Venta—; the second one is the SRI's response. They are different facts
      and they travel in different fields.

      It is **optional**: if your provider does not issue it, do not send it. Fire does not
      assemble it from `establecimiento`, `puntoEmision` and `secuencial` —that rule belongs to
      the regime— and the document is left without a visible number instead of showing one we
      put together ourselves.
    </Note>

    ```json theme={null}
    {
      "countryCode": "EC",
      "eventType": "authorized",
      "providerEventId": "evt-ec-1",
      "occurredAt": "2026-04-26T14:32:11.000Z",
      "orderId": "550e8400-e29b-41d4-a716-446655440000",
      "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "document": {
        "docType": "invoice",
        "docSubtype": "factura",
        "numeroComprobante": "001-020-000000123",
        "claveAcceso": "0102030405060708091011121314151617181920212223242",
        "numeroAutorizacion": "AUT-EC-001",
        "ambiente": "2",
        "emittedAt": "2026-04-26T14:32:10.000Z",
        "totalAmount": 24.50,
        "taxAmount": 3.15,
        "currencyCode": "USD",
        "pdfUrl": "https://api.fiscal-provider.example/sri/AUT-EC-001/pdf",
        "xmlUrl": "https://api.fiscal-provider.example/sri/AUT-EC-001/xml"
      }
    }
    ```
  </Tab>

  <Tab title="Chile (CL)">
    Chilean fiscal documents (SII DTE).

    | Field     | Type           | Required | Notes                                             |
    | --------- | -------------- | -------- | ------------------------------------------------- |
    | `folio`   | int / string   | ✓        | DTE folio number                                  |
    | `ted`     | string         | ✓        | TED (Timbre Electrónico) — base64 or XML fragment |
    | `tipoDte` | int / string   | ✓        | DTE type (e.g. `33` = factura electrónica)        |
    | `trackId` | string \| null |          | SII tracking ID for follow-up queries             |

    ```json theme={null}
    {
      "countryCode": "CL",
      "eventType": "authorized",
      "providerEventId": "evt-cl-1",
      "occurredAt": "2026-04-26T14:32:11.000Z",
      "orderId": "550e8400-e29b-41d4-a716-446655440000",
      "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "document": {
        "docType": "invoice",
        "docSubtype": "factura",
        "folio": 12345,
        "ted": "<TED>...base64-or-xml...</TED>",
        "tipoDte": 33,
        "trackId": "SII-TRK-998877",
        "emittedAt": "2026-04-26T14:32:10.000Z",
        "totalAmount": 18900,
        "taxAmount": 3019,
        "currencyCode": "CLP",
        "pdfUrl": "https://api.fiscal-provider.example/sii/12345/pdf",
        "xmlUrl": "https://api.fiscal-provider.example/sii/12345/xml"
      }
    }
    ```
  </Tab>

  <Tab title="Argentina (AR)">
    Argentine fiscal documents (AFIP).

    | Field               | Type         | Required | Notes                              |
    | ------------------- | ------------ | -------- | ---------------------------------- |
    | `cae`               | string       | ✓        | Código de Autorización Electrónico |
    | `fechaVtoCae`       | string       | ✓        | CAE expiration date                |
    | `puntoVenta`        | int / string | ✓        | Point of sale number               |
    | `numeroComprobante` | int / string | ✓        | Document number                    |
    | `tipoComprobante`   | int / string | ✓        | Document type (`1` = factura A, …) |

    ```json theme={null}
    {
      "countryCode": "AR",
      "eventType": "authorized",
      "providerEventId": "evt-ar-1",
      "occurredAt": "2026-04-26T14:32:11.000Z",
      "orderId": "550e8400-e29b-41d4-a716-446655440000",
      "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "document": {
        "docType": "invoice",
        "docSubtype": "factura_a",
        "cae": "74256178925412",
        "fechaVtoCae": "2026-05-31",
        "puntoVenta": 1,
        "numeroComprobante": 12345,
        "tipoComprobante": 1,
        "emittedAt": "2026-04-26T14:32:10.000Z",
        "totalAmount": 12100.00,
        "taxAmount": 2100.00,
        "currencyCode": "ARS",
        "pdfUrl": "https://api.fiscal-provider.example/afip/0001-12345/pdf",
        "xmlUrl": "https://api.fiscal-provider.example/afip/0001-12345/xml"
      }
    }
    ```
  </Tab>

  <Tab title="Venezuela (VE)">
    Venezuelan fiscal documents (SENIAT).

    | Field           | Type           | Required | Notes                                          |
    | --------------- | -------------- | -------- | ---------------------------------------------- |
    | `numeroControl` | string         | ✓        | Number from the control range issued by SENIAT |
    | `numeroFactura` | string         | ✓        | Invoice number                                 |
    | `rifEmisor`     | string \| null |          | Emitter RIF                                    |

    ```json theme={null}
    {
      "countryCode": "VE",
      "eventType": "authorized",
      "providerEventId": "evt-ve-1",
      "occurredAt": "2026-04-26T14:32:11.000Z",
      "orderId": "550e8400-e29b-41d4-a716-446655440000",
      "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "document": {
        "docType": "invoice",
        "docSubtype": "factura",
        "numeroControl": "00-00012345",
        "numeroFactura": "12345",
        "rifEmisor": "J-12345678-9",
        "emittedAt": "2026-04-26T14:32:10.000Z",
        "totalAmount": 480.00,
        "taxAmount": 76.80,
        "currencyCode": "VES",
        "pdfUrl": "https://api.fiscal-provider.example/seniat/00-00012345/pdf",
        "xmlUrl": "https://api.fiscal-provider.example/seniat/00-00012345/xml"
      }
    }
    ```
  </Tab>
</Tabs>

## The `fiscal_graphic` state

`fiscal_graphic` reports that your provider is delivering the **representación gráfica** — the printable artifact (PDF / RIDE / DANFE) for the document. It is a **state of its own**, not an outcome:

* **It is not an approval.** Receiving `fiscal_graphic` says nothing about whether the authority approved the document. Fire stores the graphic and the document stays non-terminal.
* **It is optional.** If your provider has no graphic for a document, send `authorized` (or any terminal state) **directly** — no `fiscal_graphic` is required first. Both flows are valid.
* **It can share the `eventId` with its outcome.** The graphic and the terminal result belong to the same action, so you may send `fiscal_graphic` and then `authorized` / `rejected` / `denied` / `cancelled` reusing the **same** `eventId`. That is not a conflict and never returns `409` — see [Idempotency & scenarios](#idempotency--scenarios).
* **It requires `document` with `pdfUrl`.** The PDF *is* the representation, so it is mandatory. `xmlUrl` stays optional here — the legal XML travels with the authorization.
* **It triggers no outbound flow event.** `order.invoiced` and `order.reversed` remain tied to `authorized` and `cancelled` respectively. The graphic only changes the document's state.

```json fiscal_graphic (CO) theme={null}
{
  "countryCode": "CO",
  "eventType": "fiscal_graphic",
  "providerEventId": "evt-co-graphic-1",
  "occurredAt": "2026-04-26T14:32:05.000Z",
  "orderId": "550e8400-e29b-41d4-a716-446655440000",
  "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "document": {
    "docType": "invoice",
    "docSubtype": "factura",
    "cufe": "633732c7a2a577bfa1828551e64d03f715f0",
    "prefijo": "C012",
    "numeroDian": "001",
    "numeroComprobante": "C012001",
    "pdfUrl": "https://api.fiscal-provider.example/dian/C012001/pdf"
  }
}
```

### 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.

```json Rejected theme={null}
{
  "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](#checking-the-outcome) 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.

<ResponseField name="received" type="boolean">
  Always `true` when the request was accepted and enqueued.
</ResponseField>

<ResponseField name="duplicate" type="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.
</ResponseField>

<ResponseField name="eventId" type="string">
  Echo of the `eventId` you sent (the Fire emission's `event.id`). Use it to correlate this acknowledgement with your request.
</ResponseField>

<ResponseField name="webhookEventId" type="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.
</ResponseField>

<ResponseField name="status" type="string">
  Current queue status of the record — `queued` → `processing` → `processed` (and `retry` / `failed` / `dead` / `ignored`).
</ResponseField>

<ResponseField name="firstReceivedAt" type="string">
  ISO 8601 UTC timestamp of when Fire **first** received this event. Stable across retries — useful as a trace anchor.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable summary of what happened.
</ResponseField>

<ResponseExample>
  ```json 202 — accepted (new, queued) theme={null}
  {
    "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."
  }
  ```

  ```json 202 — duplicate (same orderId + eventId + eventType) theme={null}
  {
    "received": true,
    "duplicate": true,
    "eventId": "1686fca1-0a26-4a89-b2f2-fe93b15a4434",
    "webhookEventId": "6d950243-6a0c-415c-b547-bddf9af8ad61",
    "status": "processed",
    "firstReceivedAt": "2026-06-11T15:00:34.729Z",
    "message": "Event already received; no action needed."
  }
  ```

  ```json 409 — eventId reused for a different action theme={null}
  {
    "success": false,
    "error": "CONFLICT",
    "message": "eventId \"1686fca1-…\" is already bound to event_type=\"authorized\" for this order. A \"cancelled\" callback must reference its own event (a distinct eventId emitted by Fire for that action), not reuse another event's id."
  }
  ```

  ```json 400 — wrong eventId / validation error theme={null}
  {
    "success": false,
    "error": "VALIDATION_ERROR",
    "message": "eventId does not reference an event emitted by Fire for this orderId. Echo the event.id from a V4 envelope you received for this order."
  }
  ```

  ```json 401 — missing or invalid API key theme={null}
  {
    "success": false,
    "error": "UNAUTHORIZED",
    "message": "API key required. Use x-api-key: pk_live_... header"
  }
  ```

  ```json 403 — wrong scope / not your tenant theme={null}
  {
    "success": false,
    "error": "FORBIDDEN",
    "message": "webhooks:fiscal requires a vendor-scoped API key (account + vendor binding). Generate one from /developers/firepos-api-management."
  }
  ```

  ```json 503 — auth store temporarily unreachable (retry) theme={null}
  {
    "success": false,
    "error": "SERVICE_UNAVAILABLE",
    "message": "API key verification is temporarily unavailable (auth store unreachable). Retry the request."
  }
  ```
</ResponseExample>

## 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>
```

<ResponseField name="status" type="string">
  Queue lifecycle: `queued` → `processing` → `processed` (done) · `failed` / `dead` (gave up after retries) · `retry` (waiting for the next attempt) · `ignored`.
</ResponseField>

<ResponseField name="attempts" type="number">Processing attempts so far.</ResponseField>
<ResponseField name="result" type="object | null">On success, the worker outcome — e.g. `{ "kind": "updated", "documentId": "…", "receiptId": "…" }`.</ResponseField>
<ResponseField name="error" type="object | null">`{ "message": "…" }` when the last attempt failed; `null` otherwise.</ResponseField>

<Note>
  The `eventType` returned here is the one **you sent** for that queued record. If you sent `fiscal_graphic` and then the terminal state under the same `eventId`, each is its own queue record with its own `webhookEventId` — poll the one you care about.
</Note>

<ResponseExample>
  ```json 200 — processed theme={null}
  {
    "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
  }
  ```
</ResponseExample>

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, eventType)`** — *not* on `providerEventId` (which you may regenerate freely). The same `(orderId, eventId)` resent with the **same** `eventType` is a benign replay; the same `(orderId, eventId)` with a **different** terminal `eventType` is an attempt to ride one action on another's event, which Fire rejects.

**`fiscal_graphic` is the exception.** It is a step of the *same* action, not a competing outcome, so it may share the `eventId` with the terminal state that follows it — that combination is accepted, never a `409`.

This is the full behavior matrix — every combination you can send:

| Scenario                                                                                                                 | Result                                                                          |
| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| New `(orderId, eventId)`                                                                                                 | **`202`** · `duplicate: false` — enqueued                                       |
| Same `(orderId, eventId, eventType)` resent (any `providerEventId`)                                                      | **`202`** · `duplicate: true` — existing record returned, **not** re-enqueued   |
| `fiscal_graphic` then a terminal state on the **same `eventId`**                                                         | **`202`** both times — the graphic is a step of the same action, not a conflict |
| Same `(orderId, eventId)` but **different terminal `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 `orderId`                                                                         | **`400`** — wrong/invented `eventId`                                            |
| Order/event not under your API key's account + vendor                                                                    | **`403`**                                                                       |
| Malformed payload (Zod), missing required fields                                                                         | **`400`**                                                                       |
| Missing / invalid API key                                                                                                | **`401`**                                                                       |
| Auth store momentarily unreachable                                                                                       | **`503`** — transient, **retry**                                                |

<Warning>
  **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.
</Warning>

<Note>
  **`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.
</Note>

## 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.

## Related

<CardGroup cols={2}>
  <Card title="order.completed" icon="receipt" href="/en/events/order-completed">
    See where the fiscal state lands inside the V4 order snapshot.
  </Card>

  <Card title="order.invoiced" icon="file-invoice" href="/en/events/order-invoiced">
    Brazil-only outbound event triggered by this callback.
  </Card>

  <Card title="Inject order" icon="paper-plane" href="/en/api-reference/orders">
    The order injection endpoint that creates the order this callback updates.
  </Card>

  <Card title="Authentication" icon="lock" href="/en/authentication">
    How API keys, scopes, and vendor binding work.
  </Card>
</CardGroup>
