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

# Callback fiscal

> Endpoint inbound al que tu proveedor fiscal hace POST cuando un documento fiscal cambia de estado. Multipaís (BR, CO, EC, CL, AR, VE). Actualiza el estado fiscal de la orden dentro de Fire y — para Brasil — dispara los eventos de orden downstream.

Este endpoint es **inbound** — tu proveedor fiscal le hace POST cada vez que un documento fiscal es autorizado, rechazado, denegado, cancelado o falla. Es la **API canónica multipaís de fiscal callback** que acepta payloads de BR, CO, EC, CL, AR, VE. Fire valida el payload, actualiza el estado fiscal de la orden en `fiscal_documents` y `orders.fiscal`, y — para Brasil — despacha eventos outbound `order.invoiced` / `order.reversed` a tus Integration Flows.

<Note>
  **Este endpoint es asíncrono.** Fire autentica, corre el guard de source-of-truth + tenancy, deduplica, pre-marca la orden como `processing` y **encola** el callback — luego devuelve **`202 Accepted`** con un `webhookEventId` (típicamente en menos de 100 ms). La actualización de `fiscal_documents` / `orders.fiscal` ocurre un instante después en un worker en segundo plano (normalmente en \~2 segundos). Para ver el resultado del procesamiento, consultá [`GET /v1/webhooks/events/{webhookEventId}`](#comprobar-el-resultado). Los problemas corregibles por el cliente (payload inválido, `eventId` equivocado, tenant equivocado, una acción reusando el evento de otra) se rechazan **sincrónicamente** con `4xx` **antes** del `202`.
</Note>

## Cómo complementa `order.completed` y `order.cancelled`

`order.completed` y `order.cancelled` llevan el **estado de negocio** de una orden. El callback fiscal lleva el **estado fiscal** (autorización SEFAZ / DIAN / SRI / SII / AFIP / SENIAT). Juntos forman este lifecycle:

```mermaid theme={null}
flowchart LR
    A([Orden pagada]) --> B([order.completed])
    B --> C[Tu proveedor fiscal<br/>emite el documento]
    C --> D([POST /v1/webhooks/fiscal/callback])
    D --> E[Fire actualiza fiscal_documents<br/>+ orders.fiscal JSONB]
    E -. solo BR .-> F([order.invoiced event])
```

Después de procesar el callback, el **siguiente** `order.completed` o `order.cancelled` para el mismo `orderId` refleja el estado fiscal actualizado en:

* `data.store.storeFiscalConfig` — contexto del emisor (CNPJ / NIT / RUC / RUT / CUIT / RIF…)
* `data.payments.metadata.fiscal` — agregados fiscales totales (BR populado; otros países `null`)
* `data.orderLines[].metadata.fiscal` — clasificación fiscal por línea (BR populado; otros `null`)

Para Brasil específicamente, también se emite un evento `order.invoiced` (o `order.reversed`) separado con las referencias del documento SEFAZ en `data.fiscal`.

<Note>
  **Hoy, solo `order.invoiced` y `order.reversed` están cableados como eventos outbound (Brasil).** El endpoint valida y guarda callbacks para los 6 países (CO, EC, CL, AR, VE, BR), y el estado fiscal actualizado aparece en el siguiente evento `order.completed` / `order.cancelled` sin importar el país. Los eventos outbound para CO/EC/CL/AR/VE están en camino.
</Note>

## Autenticación

Este endpoint requiere una **API key con scope `webhooks:fiscal`**, vendor-scoped al account dueño de la orden. Las keys sin scope o solo a nivel account se rechazan con `403 Forbidden`.

<ParamField header="x-api-key" type="string" required>
  Tu API key de Fire. Générala desde el dashboard en **Settings → API Keys → Developer keys**, con scope `webhooks:fiscal` y un binding de vendor para el account cuyas órdenes esta key puede actualizar.
</ParamField>

<ParamField header="Authorization" type="string">
  Opcional `Bearer <token>`. No se requiere para este endpoint hoy, pero está reservado para futuros tokens emitidos por proveedor.
</ParamField>

## Body de la petición

El body es un objeto JSON con estos campos top-level. La forma del `document` está **discriminada por `countryCode`** — ver [Documento por país](#documento-por-pais) abajo.

<ParamField body="countryCode" type="string" required>
  Código de país ISO 3166-1 alpha-2. Uno de `BR`, `CO`, `EC`, `CL`, `AR`, `VE`. Determina las reglas de validación del objeto `document`.
</ParamField>

<ParamField body="eventType" type="string" required>
  Estado del documento. Uno de:

  * `fiscal_graphic` — el proveedor está entregando la **representación gráfica** (el artefacto imprimible) del documento. **No es una aprobación** ni un estado terminal — ver [El estado `fiscal_graphic`](#el-estado-fiscal_graphic) abajo.
  * `authorized` — la autoridad fiscal aprobó el documento
  * `cancelled` — un documento previamente autorizado fue cancelado
  * `rejected` — el documento fue rechazado (validación, schema, firma)
  * `denied` — la autoridad denegó la solicitud (típicamente fallo permanente de regla de negocio)
  * `error` — ocurrió un error no recuperable en el proveedor o autoridad

  Cuando `eventType` es `fiscal_graphic`, `authorized` o `cancelled`, el campo `document` es **requerido**. Cuando es `rejected`, `denied` o `error`, el campo `error` es **requerido**.
</ParamField>

<ParamField body="providerEventId" type="string" required>
  El id propio de tu proveedor para esta entrega. Se guarda para auditoría/forense — **no es la llave de idempotencia**. Fire deduplica por `(orderId, eventId)`, así que podés enviar un `providerEventId` nuevo en cada reintento sin crear duplicados. Usa el ID nativo del evento del proveedor si está disponible; si no, un UUID.
</ParamField>

<ParamField body="occurredAt" type="string" required>
  Timestamp ISO 8601 UTC de cuándo ocurrió el evento en la autoridad fiscal (no cuándo el proveedor envió el callback).
</ParamField>

<ParamField body="orderId" type="string" required>
  UUID de la orden en Fire. Coincide con `data.orderId` en los eventos `order.completed` y `order.cancelled`. Fire lo usa para encontrar el documento fiscal existente.
</ParamField>

<ParamField body="eventId" type="string" required>
  UUID de correlación **y llave de idempotencia** (junto con `orderId`). Debe ser el `event.id` de un envelope V4 que **Fire emitió para esta orden** — ecoalo exacto; nunca lo inventes.

  * **Chequeo de fuente de verdad.** Un `eventId` que no referencie un evento de Fire para esa orden se rechaza con `400` antes del `202`.
  * **Cada acción lleva su propio evento.** Ecoa el `event.id` del evento que disparó *esta* acción — la emisión `order.invoiced` para un callback `authorized`, el evento de cancelación/reverso para un callback `cancelled`. Reusar el `eventId` de una acción para otro `eventType` (ej. un `cancelled` que ecoa el `eventId` del `authorized`) se rechaza con **`409`** — una cancelación debe referenciar su **propio** evento, no colgarse del de la autorización. Ver [Idempotencia y escenarios](#idempotencia-y-escenarios).
</ParamField>

<ParamField body="document" type="object">
  El documento fiscal autorizado / cancelado. **Requerido** cuando `eventType` es `authorized` o `cancelled`. La forma varía por país — ver [Documento por país](#documento-por-pais).
</ParamField>

<ParamField body="error" type="object">
  Contexto de error para resultados negativos. **Requerido** cuando `eventType` es `rejected`, `denied` o `error`.

  <Expandable title="error">
    <ParamField body="code" type="string">
      Código de error opcional del proveedor/autoridad (p. ej. `DIAN_42`, `cStat_204`).
    </ParamField>

    <ParamField body="message" type="string" required>
      Mensaje legible. Mínimo 1 carácter.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="providerSpecific" type="object">
  Bag free-form para extras específicos del proveedor (respuesta raw, IDs internos, etc.). No validado; pasa al log de auditoría.
</ParamField>

## Documento por país

La forma requerida del campo `document` depende del `countryCode`. **Todas las variantes comparten los campos base de abajo** (comunes a los 6 países) más los identificadores específicos del país requeridos por esa autoridad.

### Campos base comunes

Aplican a cualquier `countryCode`. `docType` y `docSubtype` son **obligatorios**; el resto son **opcionales** — envíalos cuando tu proveedor fiscal los tenga disponibles. Fire los persiste en `fiscal_documents` / `orders.fiscal` y los reemite en los eventos outbound `order.invoiced` / `order.reversed` (Brasil) y en el siguiente `order.completed` / `order.cancelled`.

| Campo          | Tipo                           | Requerido | Notas                                                                                 |
| -------------- | ------------------------------ | --------- | ------------------------------------------------------------------------------------- |
| `docType`      | `"invoice"` / `"cancellation"` | ✓         | Clase del documento — `invoice` para emisión, `cancellation` para cancelación         |
| `docSubtype`   | string                         | ✓         | Subtipo según país/proveedor (p. ej. `nfce`, `nfe`, `factura`, `boleta`, `factura_a`) |
| `pdfUrl`       | string (URL) \| null           |           | Enlace de descarga del PDF del documento (DANFE / representación gráfica)             |
| `xmlUrl`       | string (URL) \| null           |           | Enlace de descarga del XML canónico de la autoridad fiscal                            |
| `emittedAt`    | string (ISO 8601) \| null      |           | Fecha/hora UTC en que la autoridad autorizó/emitió el documento                       |
| `cancelledAt`  | string (ISO 8601) \| null      |           | Fecha/hora UTC de la cancelación — relevante cuando `eventType` es `cancelled`        |
| `totalAmount`  | number \| null                 |           | Monto total bruto del documento                                                       |
| `taxAmount`    | number \| null                 |           | Monto total de impuestos                                                              |
| `currencyCode` | string \| null                 |           | Código de moneda ISO 4217 — exactamente 3 letras (p. ej. `BRL`, `COP`, `USD`)         |

<Note>
  `pdfUrl` y `xmlUrl` se guardan **tal cual los envías** — Fire no descarga ni re-hostea el archivo. Si tu proveedor firma estas URLs con expiración, ten en cuenta que el enlace almacenado puede caducar; descarga y persiste el artefacto por tu lado si necesitas acceso durable.
</Note>

<Tabs>
  <Tab title="Brasil (BR)">
    Documentos fiscales brasileños (NF-e / NFC-e). Usa este código de país al enviar callbacks desde tu proveedor fiscal o cualquier otro proveedor BR.

    Además de los [campos base comunes](#campos-base-comunes) (`docType`, `docSubtype`, `pdfUrl`, `xmlUrl`, `emittedAt`, etc.), BR requiere estos identificadores específicos:

    | Campo          | Tipo                 | Requerido | Notas                                          |
    | -------------- | -------------------- | --------- | ---------------------------------------------- |
    | `chaveAcesso`  | string               | ✓         | Exactamente 44 dígitos — chave de acceso SEFAZ |
    | `protocolo`    | string               | ✓         | Protocolo de autorización SEFAZ                |
    | `numero`       | int / string         | ✓         | Número de documento                            |
    | `serie`        | int / string \| null |           | Serie del documento (codificada en chave)      |
    | `modelo`       | `55` / `65` \| null  |           | `55` = NF-e, `65` = NFC-e                      |
    | `cnpjEmitente` | string \| null       |           | CNPJ del emisor                                |

    El ejemplo de abajo incluye los campos base opcionales (`pdfUrl`, `xmlUrl`, `emittedAt`, `totalAmount`, `taxAmount`, `currencyCode`) — todos pueden omitirse, pero si tu proveedor los tiene, envíalos:

    ```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)">
    Documentos fiscales colombianos (DIAN).

    | Campo               | Tipo                   | Requerido | Notas                                                     |
    | ------------------- | ---------------------- | --------- | --------------------------------------------------------- |
    | `cufe`              | string                 | ✓         | Código fiscal único DIAN                                  |
    | `prefijo`           | string                 | ✓         | Prefijo del documento                                     |
    | `numeroDian`        | string                 | ✓         | Número de documento DIAN                                  |
    | `qrCode`            | string (URL) \| null   |           | QR para el documento impreso                              |
    | `numeroComprobante` | string \| null         |           | El número **visible**, ya armado: `prefijo + consecutivo` |
    | `ambiente`          | `"1"` \| `"2"` \| null |           | **`1` producción · `2` pruebas** — al revés que el 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>
      **Los nombres son los mismos que en la numeración**, a propósito: `cufe`, `prefijo`,
      `numeroDian`, `numeroComprobante`, `qrCode` y `ambiente` significan acá exactamente lo
      mismo que en la respuesta del prekey. Es el mismo documento contado dos veces, y si los
      nombres divergieran, conciliar los dos caminos dejaría de ser comparar campos.

      **`numeroDian` va sin el prefijo** (`990000001`, no `SETP990000001`). El número armado es
      `numeroComprobante`. Mandar el armado en los dos hace que la conciliación compare
      `SETP990000001` contra `990000001` y no cruce.

      Los dos campos nuevos son **opcionales**: quien ya manda callbacks sin ellos sigue
      funcionando. El callback nunca se rechaza por un campo que falte — cuando llega, el
      documento ya existe ante la DIAN.
    </Note>
  </Tab>

  <Tab title="Ecuador (EC)">
    Documentos fiscales ecuatorianos (SRI).

    | Campo                | Tipo                  | Requerido | Notas                                                       |
    | -------------------- | --------------------- | --------- | ----------------------------------------------------------- |
    | `claveAcceso`        | string                | ✓         | Exactamente 49 dígitos — clave de acceso SRI                |
    | `numeroAutorizacion` | string                | ✓         | Número de autorización SRI                                  |
    | `numeroComprobante`  | string \| null        |           | El número **visible**, ya armado: `estab-ptoEmi-secuencial` |
    | `ambiente`           | `'1'` / `'2'` \| null |           | `1` = pruebas, `2` = producción                             |

    <Note>
      **`numeroComprobante` no es `numeroAutorizacion`.** El primero es el número que va
      impreso en el comprobante —quince dígitos en tres tramos, art. 18 del Reglamento de
      Comprobantes de Venta—; el segundo es la respuesta del SRI. Son hechos distintos y
      viajan en campos distintos.

      Es **opcional**: si tu proveedor no lo emite, no lo mandes. Fire no lo compone a partir
      de `establecimiento`, `puntoEmision` y `secuencial` —esa regla es del régimen— y el
      documento queda sin número visible en lugar de mostrar uno armado por nosotros.
    </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)">
    Documentos fiscales chilenos (SII DTE).

    | Campo     | Tipo           | Requerido | Notas                                             |
    | --------- | -------------- | --------- | ------------------------------------------------- |
    | `folio`   | int / string   | ✓         | Número de folio del DTE                           |
    | `ted`     | string         | ✓         | TED (Timbre Electrónico) — base64 o fragmento XML |
    | `tipoDte` | int / string   | ✓         | Tipo DTE (p. ej. `33` = factura electrónica)      |
    | `trackId` | string \| null |           | Track ID SII para queries de seguimiento          |

    ```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)">
    Documentos fiscales argentinos (AFIP).

    | Campo               | Tipo         | Requerido | Notas                                  |
    | ------------------- | ------------ | --------- | -------------------------------------- |
    | `cae`               | string       | ✓         | Código de Autorización Electrónico     |
    | `fechaVtoCae`       | string       | ✓         | Fecha de vencimiento del CAE           |
    | `puntoVenta`        | int / string | ✓         | Número de punto de venta               |
    | `numeroComprobante` | int / string | ✓         | Número del documento                   |
    | `tipoComprobante`   | int / string | ✓         | Tipo de documento (`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)">
    Documentos fiscales venezolanos (SENIAT).

    | Campo           | Tipo           | Requerido | Notas                                          |
    | --------------- | -------------- | --------- | ---------------------------------------------- |
    | `numeroControl` | string         | ✓         | Número del rango de control emitido por SENIAT |
    | `numeroFactura` | string         | ✓         | Número de factura                              |
    | `rifEmisor`     | string \| null |           | RIF del emisor                                 |

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

## El estado `fiscal_graphic`

`fiscal_graphic` reporta que tu proveedor está entregando la **representación gráfica** — el artefacto imprimible (PDF / RIDE / DANFE) del documento. Es un **estado propio**, no un resultado:

* **No es una aprobación.** Recibir `fiscal_graphic` no dice nada sobre si la autoridad aprobó el documento. Fire guarda la gráfica y el documento sigue en un estado no terminal.
* **Es opcional.** Si tu proveedor no tiene gráfica para un documento, envía `authorized` (o cualquier estado terminal) **directamente** — no hace falta un `fiscal_graphic` previo. Ambos flujos son válidos.
* **Puede compartir el `eventId` con su desenlace.** La gráfica y el resultado terminal pertenecen a la misma acción, así que podés enviar `fiscal_graphic` y luego `authorized` / `rejected` / `denied` / `cancelled` reusando el **mismo** `eventId`. Eso no es un conflicto y nunca devuelve `409` — ver [Idempotencia y escenarios](#idempotencia-y-escenarios).
* **Requiere `document` con `pdfUrl`.** El PDF *es* la representación, así que es obligatorio. `xmlUrl` queda opcional acá — el XML legal viaja con la autorización.
* **No dispara ningún evento saliente.** `order.invoiced` y `order.reversed` siguen atados a `authorized` y `cancelled` respectivamente. La gráfica solo cambia el estado del documento.

```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"
  }
}
```

### Resultados negativos (`rejected`, `denied`, `error`)

Para estados no autorizados, omite `document` y provee `error`. El `countryCode` sigue aplicando (valida el routing); el documento no se requiere porque no hay artefacto autorizado.

```json Rechazado 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"
  }
}
```

## Respuesta

En caso de éxito el endpoint devuelve **`202 Accepted`** — el evento fue autenticado, validado, deduplicado y **encolado**. Un `202` **no** significa que el documento fiscal ya se actualizó; eso ocurre de forma asíncrona en un worker en segundo plano. Usá el [endpoint de status](#comprobar-el-resultado) para confirmar el resultado final.

El body trae **dos ids distintos** para que no haya ambigüedad: `eventId` es el id que **vos** enviaste (devuelto como echo), `webhookEventId` es el id de **Fire** para el registro encolado.

<ResponseField name="received" type="boolean">
  Siempre `true` cuando la petición fue aceptada y encolada.
</ResponseField>

<ResponseField name="duplicate" type="boolean">
  `true` cuando este `(orderId, eventId)` ya se ingresó **con el mismo `eventType`** — se devuelve el registro existente y no se re-encola nada. `false` para un evento nuevo.
</ResponseField>

<ResponseField name="eventId" type="string">
  Echo del `eventId` que enviaste (el `event.id` de la emisión de Fire). Usalo para casar este acuse con tu envío.
</ResponseField>

<ResponseField name="webhookEventId" type="string">
  El id de Fire para el registro encolado en `webhook_events`. Pasalo a `GET /v1/webhooks/events/{webhookEventId}` para consultar el resultado del procesamiento. En un duplicado es el **mismo** id que se devolvió la primera vez.
</ResponseField>

<ResponseField name="status" type="string">
  Status actual del registro en la cola — `queued` → `processing` → `processed` (y `retry` / `failed` / `dead` / `ignored`).
</ResponseField>

<ResponseField name="firstReceivedAt" type="string">
  Timestamp ISO 8601 UTC de cuándo Fire recibió **por primera vez** este evento. Estable entre reintentos — útil como ancla de traza.
</ResponseField>

<ResponseField name="message" type="string">
  Resumen legible de lo que pasó.
</ResponseField>

<ResponseExample>
  ```json 202 — aceptado (nuevo, encolado) 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 — duplicado (mismo 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 reusado para otra acción 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 — eventId incorrecto / error de validación 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 — API key faltante o inválida theme={null}
  {
    "success": false,
    "error": "UNAUTHORIZED",
    "message": "API key required. Use x-api-key: pk_live_... header"
  }
  ```

  ```json 403 — scope incorrecto / no es tu 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 temporalmente inalcanzable (reintentá) theme={null}
  {
    "success": false,
    "error": "SERVICE_UNAVAILABLE",
    "message": "API key verification is temporarily unavailable (auth store unreachable). Retry the request."
  }
  ```
</ResponseExample>

## Comprobar el resultado

Como el procesamiento es asíncrono, el `202` solo confirma que el evento fue **encolado**. Para ver si el documento fiscal se actualizó, consultá el endpoint de status con el `webhookEventId` devuelto por el `202`:

```
GET https://app.fire.rest/api/v1/webhooks/events/{webhookEventId}
x-api-key: <tu key webhooks:fiscal>
```

<ResponseField name="status" type="string">
  Ciclo de vida en la cola: `queued` → `processing` → `processed` (listo) · `failed` / `dead` (se agotaron los reintentos) · `retry` (esperando el próximo intento) · `ignored`.
</ResponseField>

<ResponseField name="attempts" type="number">Intentos de procesamiento hasta ahora.</ResponseField>
<ResponseField name="result" type="object | null">En éxito, el resultado del worker — ej. `{ "kind": "updated", "documentId": "…", "receiptId": "…" }`.</ResponseField>
<ResponseField name="error" type="object | null">`{ "message": "…" }` cuando el último intento falló; `null` si no.</ResponseField>

<ResponseExample>
  ```json 200 — procesado 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>

Un `404` se devuelve para ids desconocidos — o ids de otro tenant — sin filtrar existencia. Autenticá con la misma key `webhooks:fiscal` que usaste para el callback.

## Idempotencia y escenarios

Fire deduplica callbacks por **`(orderId, eventId)`** — *no* por `providerEventId` (que podés regenerar libremente). Un evento es único con su orden: el mismo `(orderId, eventId)` reenviado con el **mismo** `eventType` es un replay benigno; el mismo `(orderId, eventId)` con un `eventType` **distinto** es un intento de colgar una acción sobre el evento de otra, y Fire lo rechaza.

**`fiscal_graphic` es la excepción.** Es un paso de la *misma* acción, no un desenlace que compita, así que puede compartir el `eventId` con el estado terminal que llega después — esa combinación se acepta, nunca da `409`.

Esta es la matriz completa de comportamiento — cada combinación que podés enviar:

| Escenario                                                                                                    | Resultado                                                                              |
| ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `(orderId, eventId)` nuevo                                                                                   | **`202`** · `duplicate: false` — encolado                                              |
| Mismo `(orderId, eventId, eventType)` reenviado (cualquier `providerEventId`)                                | **`202`** · `duplicate: true` — se devuelve el registro existente, **no** se re-encola |
| `fiscal_graphic` y luego un estado terminal con el **mismo `eventId`**                                       | **`202`** ambas veces — la gráfica es un paso de la misma acción, no un conflicto      |
| Mismo `(orderId, eventId)` pero **`eventType` distinto** (ej. `cancelled` con el `eventId` del `authorized`) | **`409`** — rechazado. Usá el evento que pertenece a *esta* acción                     |
| `eventId` no emitido por Fire para ese `orderId`                                                             | **`400`** — `eventId` incorrecto/inventado                                             |
| Orden/evento fuera de la cuenta + vendor de tu API key                                                       | **`403`**                                                                              |
| Payload malformado (Zod), faltan campos obligatorios                                                         | **`400`**                                                                              |
| API key faltante / inválida                                                                                  | **`401`**                                                                              |
| Auth store momentáneamente inalcanzable                                                                      | **`503`** — transitorio, **reintentá**                                                 |

<Warning>
  **Una cancelación necesita su propio evento.** No podés cancelar un documento reenviando el `eventId` de la autorización con `eventType: "cancelled"` — eso devuelve `409`. Fire es la fuente de verdad: una cancelación debe referenciar el **evento de cancelación/reverso que Fire emitió** (un `eventId` distinto). Devolver un `202 duplicado` aquí te diría erróneamente que la cancelación fue aceptada mientras Fire nunca canceló — por eso Fire lo rechaza explícitamente.
</Warning>

<Note>
  **`409` vs `202`.** Un `409` es el único caso donde un callback *bien formado y autenticado* se rechaza en la capa de idempotencia — porque aceptarlo divergiría el estado. Un replay del mismo tipo nunca es un error: devuelve `202` para que tus reintentos queden limpios. Un `503` es **nuestro** (infra transitoria), así que es seguro y esperado reintentar.
</Note>

## Concurrencia

Las transiciones de estado en `fiscal_documents` usan **locking optimista**. Si dos callbacks para el mismo documento llegan concurrentes, uno tiene éxito y el otro resuelve a `idempotent` o `regression` según el orden. El merge de `orders.fiscal` JSONB es atómico.

## Qué pasa después del 202

El `202` solo encola el evento. Un worker en segundo plano lo toma — en \~2 segundos vía el wake-up instantáneo, o en el próximo ciclo de polling como fallback — y:

1. **La fila `fiscal_documents` se hace upsert** con el nuevo status y referencias del documento (`chaveAcesso`, `protocolo`, `cufe`, `cae`, etc., según país).
2. **La columna `orders.fiscal` JSONB se mergea** con la misma data — visible en el siguiente evento `order.completed` / `order.cancelled` para el mismo `orderId`, sin importar el país.
3. **Se despacha un trigger outbound** — para Brasil, `triggerType = 'order.invoiced'` (para `eventType=authorized`) o `'order.reversed'` (para `eventType=cancelled`). Los Integration Flows activos para ese trigger corren y hacen POST a tu endpoint.

* **Hoy**, solo `order.invoiced` y `order.reversed` están cableados. Disparan cuando un callback authorized/cancelled para una tienda BR se procesa.
* **Para CO/EC/CL/AR/VE**, los eventos outbound equivalentes aún no están cableados — los callbacks se validan y guardan, y el estado fiscal se refleja en el siguiente `order.completed` / `order.cancelled` para la misma orden.

## Relacionado

<CardGroup cols={2}>
  <Card title="order.completed" icon="receipt" href="/es/events/order-completed">
    Ve dónde aterriza el estado fiscal dentro del snapshot V4 de la orden.
  </Card>

  <Card title="order.invoiced" icon="file-invoice" href="/es/events/order-invoiced">
    Evento outbound BR-only disparado por este callback.
  </Card>

  <Card title="Inyectar orden" icon="paper-plane" href="/es/api-reference/orders">
    El endpoint de inyección que crea la orden que este callback actualiza.
  </Card>

  <Card title="Autenticación" icon="lock" href="/es/authentication">
    Cómo funcionan API keys, scopes y vendor binding.
  </Card>
</CardGroup>
