Saltar al contenido principal
POST
/
api
/
v1
/
webhooks
/
fiscal
/
callback
Callback fiscal
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."
}
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.
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}. 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.

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

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.
x-api-key
string
requerido
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.
Authorization
string
Opcional Bearer <token>. No se requiere para este endpoint hoy, pero está reservado para futuros tokens emitidos por proveedor.

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 abajo.
countryCode
string
requerido
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.
eventType
string
requerido
Estado del documento. Uno de:
  • 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 authorized o cancelled, el campo document es requerido. Cuando es rejected, denied o error, el campo error es requerido.
providerEventId
string
requerido
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.
occurredAt
string
requerido
Timestamp ISO 8601 UTC de cuándo ocurrió el evento en la autoridad fiscal (no cuándo el proveedor envió el callback).
orderId
string
requerido
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.
eventId
string
requerido
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.
document
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.
error
object
Contexto de error para resultados negativos. Requerido cuando eventType es rejected, denied o error.
providerSpecific
object
Bag free-form para extras específicos del proveedor (respuesta raw, IDs internos, etc.). No validado; pasa al log de auditoría.

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.
CampoTipoRequeridoNotas
docType"invoice" / "cancellation"Clase del documento — invoice para emisión, cancellation para cancelación
docSubtypestringSubtipo según país/proveedor (p. ej. nfce, nfe, factura, boleta, factura_a)
pdfUrlstring (URL) | nullEnlace de descarga del PDF del documento (DANFE / representación gráfica)
xmlUrlstring (URL) | nullEnlace de descarga del XML canónico de la autoridad fiscal
emittedAtstring (ISO 8601) | nullFecha/hora UTC en que la autoridad autorizó/emitió el documento
cancelledAtstring (ISO 8601) | nullFecha/hora UTC de la cancelación — relevante cuando eventType es cancelled
totalAmountnumber | nullMonto total bruto del documento
taxAmountnumber | nullMonto total de impuestos
currencyCodestring | nullCódigo de moneda ISO 4217 — exactamente 3 letras (p. ej. BRL, COP, USD)
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.
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 (docType, docSubtype, pdfUrl, xmlUrl, emittedAt, etc.), BR requiere estos identificadores específicos:
CampoTipoRequeridoNotas
chaveAcessostringExactamente 44 dígitos — chave de acceso SEFAZ
protocolostringProtocolo de autorización SEFAZ
numeroint / stringNúmero de documento
serieint / string | nullSerie del documento (codificada en chave)
modelo55 / 65 | null55 = NF-e, 65 = NFC-e
cnpjEmitentestring | nullCNPJ 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:
{
  "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"
  }
}

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.
Rechazado
{
  "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 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.
received
boolean
Siempre true cuando la petición fue aceptada y encolada.
duplicate
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.
eventId
string
Echo del eventId que enviaste (el event.id de la emisión de Fire). Usalo para casar este acuse con tu envío.
webhookEventId
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.
status
string
Status actual del registro en la cola — queuedprocessingprocessed (y retry / failed / dead / ignored).
firstReceivedAt
string
Timestamp ISO 8601 UTC de cuándo Fire recibió por primera vez este evento. Estable entre reintentos — útil como ancla de traza.
message
string
Resumen legible de lo que pasó.
{
  "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."
}

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>
status
string
Ciclo de vida en la cola: queuedprocessingprocessed (listo) · failed / dead (se agotaron los reintentos) · retry (esperando el próximo intento) · ignored.
attempts
number
Intentos de procesamiento hasta ahora.
result
object | null
En éxito, el resultado del worker — ej. { "kind": "updated", "documentId": "…", "receiptId": "…" }.
error
object | null
{ "message": "…" } cuando el último intento falló; null si no.
{
  "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
}
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. Esta es la matriz completa de comportamiento — cada combinación que podés enviar:
EscenarioResultado
(orderId, eventId) nuevo202 · duplicate: false — encolado
Mismo (orderId, eventId, eventType) reenviado (cualquier providerEventId)202 · duplicate: true — se devuelve el registro existente, no se re-encola
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 orderId400eventId incorrecto/inventado
Orden/evento fuera de la cuenta + vendor de tu API key403
Payload malformado (Zod), faltan campos obligatorios400
API key faltante / inválida401
Auth store momentáneamente inalcanzable503 — transitorio, reintentá
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.
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.

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

order.completed

Ve dónde aterriza el estado fiscal dentro del snapshot V4 de la orden.

order.invoiced

Evento outbound BR-only disparado por este callback.

Inyectar orden

El endpoint de inyección que crea la orden que este callback actualiza.

Autenticación

Cómo funcionan API keys, scopes y vendor binding.