Saltar al contenido principal
POST
Callback fiscal
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, guarda el veredicto del ente en la orden y despacha order.invoiced / order.reversed a tus Integration Flows — para todos los países. Lo que mandás acá es lo que leen los consumidores de los eventos: ver Del callback a los eventos.
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 un callback authorized, Fire emite order.invoiced; después de uno cancelled, order.reversed. Ambos salen para todos los países — el país viaja en el bloque fiscal del evento, no en su nombre. Los eventos posteriores de la orden para el mismo orderId también reflejan el nuevo estado en data.lastKnown.fiscal. Para el panorama fiscal completo — numeración, callback y eventos, y cómo encajan — ver el panorama fiscal.

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.
string
requerido
Tu API key de Fire. Générala desde el dashboard en Developers → API Management, con scope webhooks:fiscal y un binding de vendor para el account cuyas órdenes esta key puede actualizar.
string
Alias legado: la misma API key de Fire, como Bearer pk_live_.... Prefiere x-api-key; si llegan los dos, se usa x-api-key.

Body de la petición

El body es un objeto JSON con estos campos top-level. Todo lo canónico vive acá arriba; document lleva SOLO el vocabulario del ente de tu país, elegido por country — ver Documento por país abajo.
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.Antes se llamaba countryCode.
string
requerido
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 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 failure es requerido.
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) — un eventType distinto sobre el mismo par devuelve 409, salvo fiscal_graphic y los reintentos después de un fallo (ver Reintentos y anulación rechazada) — 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.
string
requerido
Timestamp ISO 8601 UTC de cuándo ocurrió el evento en la autoridad fiscal (no cuándo el proveedor envió el callback).
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.
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.
object
El vocabulario del ente de tu país, y nada más: claveAcceso, cufe, chaveAcesso, folio, cae… Requerido cuando eventType es authorized, cancelled o fiscal_graphic. Ver Documento por país.Los campos comunes —documentType, el número, las URLs, los importes, las fechas— no van acá adentro: están en la raíz. Todo lo que va en document llega a los eventos como authority.countryData, con los mismos nombres de clave.

Campos canónicos (raíz)

Significan lo mismo en los seis países, así que viven en la raíz del payload y no dentro de document. Es la misma regla que la respuesta de numeración fiscal, para que el mismo hecho no se busque en dos profundidades distintas según por dónde entró.
string
SALE_INVOICE o CREDIT_NOTE — el mismo vocabulario que la numeración fiscal. Requerido cuando hay comprobante (authorized / cancelled), y tiene que coincidir con el evento: authorized es un SALE_INVOICE, cancelled es un CREDIT_NOTE. Cualquier otra combinación devuelve 400.Antes era docType: "invoice" | "cancellation". El nombre viejo se ignora, no se traduce: un payload authorized o cancelled que trae solo docType recibe 400 en documentType.
string
Cómo lo llama tu régimen: nfce, nfe, factura, boleta, factura_a. Abierto: no lo validamos contra una lista y no ramifica nada de nuestro lado — es referencial. Requerido cuando hay comprobante.

Tipos de documento

Fire acepta exactamente dos valores en documentType. No hay un tercer tipo: cualquier otro valor devuelve 400. Cómo llama cada régimen a su documento va en docSubtype, y ese campo es abierto: nfce, nfe (Brasil), factura (Ecuador, Colombia), boleta (Chile), factura_a (Argentina) y cualquier otro nombre que use tu autoridad. Fire no lo valida contra una lista ni decide nada con él.
No lo confundas con el catálogo de tipos de documento: ese endpoint lista los documentos de identificación del comprador (CEDULA, CPF, NIT…), no tipos de documento fiscal.
string
El número visible del documento, tal como lo arma el ente (12388, 001-020-000000123, C012001). Las piezas con las que se arma siguen viajando en document con los nombres del país. Requerido en authorized: Fire ya no lo deduce.
string
Fecha/hora UTC (ISO 8601) en que se emitió el documento. Mismo nombre que en la numeración fiscal. Requerido en authorized.
string
Fecha/hora UTC (ISO 8601) en que el ente aprobó el documento — un hecho distinto de issuedAt. Pueden separarse por segundos (autorización en línea) o por horas (contingencia autorizada cuando vuelve la conexión). Opcional; si no viene, los consumidores de los eventos ven authority.authorizedAt: null. En una cancelación usa cancelledAt: un callback cancelled conserva el issuedAt y el authorizedAt que el documento ya tenía —son los de la factura— y solo usa los que mandes si Fire no tenía ninguno.
string
Fecha/hora UTC en que el ente aprobó la cancelación. Mandalo en cancelled.
string
ONLINE, OFFLINE o BATCH. OFFLINE es contingencia: el comprobante vale pero se emitió sin poder consultar al ente, y hay regímenes que exigen que el ticket lo diga (el RIDE ecuatoriano imprime EMISION POR CONTINGENCIA).
string
Enlace de descarga del PDF (DANFE / RIDE). HTTPS, descargable con un GET directo, válido por al menos 24 h — Fire lo archiva. Requerido para authorized y cancelled.
string
Enlace de descarga del XML legal del ente. Mismas condiciones que pdfUrl. Requerido para authorized y cancelled.
number
Total bruto del documento, en decimales reales (sin escalar). Requerido en authorized.
number
Total de impuestos del documento, en decimales reales. Requerido en authorized.
string
Código ISO 4217 — exactamente 3 letras (BRL, COP, USD). Requerido en authorized.
string
El id del documento en tu sistema. Es por lo que se buscan el PDF y el XML y con lo que se pide una cancelación. Requerido en authorized.
object
Lo que se imprime: un mapa clave → string exacto a codificar ({ "qr": "…" }, { "ted": "…" }). La clave nombra el propósito; cómo se dibuja lo decide la plantilla del ticket. Va en la raíz, al lado de document, como en la respuesta de numeración fiscal. Requerido y no vacío en fiscal_graphic; opcional en el resto.
object
Quién emitió y con qué versión. Llega a la orden y a los eventos como authority.providerIdentity — el mismo nombre que en la numeración fiscal — con las tres claves siempre presentes. Si no mandás provider, o lo mandás vacío, providerIdentity es null.
object
Por qué NO hay comprobante. Requerido cuando eventType es rejected, denied o error.Antes se llamaba error, y ahora lleva la misma forma que la respuesta de numeración fiscal.
object
Bag free-form para extras específicos del proveedor (respuesta raw, IDs internos, etc.). No validado. Llega a la orden y a los eventos como authority.providerMetadata, tal cual y opaco — igual que en la numeración fiscal. Ausente o {} queda en null. No mandes null: omítelo o manda {} — null devuelve 400.Antes se llamaba providerSpecific.
La raíz no admite claves extra. Solo se leen los campos de arriba; cualquier otra clave de raíz se descarta en silencio — no se rechaza. orderCode no es parte del contrato — la orden se resuelve por orderId.

Qué es requerido, por evento

authorizedAt, cancelledAt, authorizationMode y provider son siempre opcionales. Un campo requerido enviado como null cuenta como faltante.

Documento por país

La forma requerida del campo document depende de country. document lleva SOLO los identificadores del ente de ese país — los campos comunes a los seis viven en la raíz del payload, arriba.
document cambió de contenidoHasta ahora document mezclaba dos cosas: lo que significa lo mismo en todos los países —docType, pdfUrl, xmlUrl, los importes, las fechas— y el vocabulario del ente de cada uno. Esa mezcla se propagaba a todo lo que leía el callback.Ahora lo canónico va en la raíz y document queda solo con lo del régimen. Es la misma forma que la respuesta de numeración fiscal: el mismo hecho, en el mismo lugar, sin importar por qué camino entró.Otros cambios del mismo paquete: countryCode → country, docType → documentType (SALE_INVOICE / CREDIT_NOTE, sin alias), error → failure (con scope y details), providerSpecific → metadata, y emittedAt → issuedAt. Nuevos en la raíz: documentNumber, authorizedAt y graphic.
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 canónicos de la raíz (documentType, docSubtype, documentNumber, pdfUrl, xmlUrl, issuedAt, etc.), BR requiere estos identificadores específicos:El ejemplo de abajo es un payload authorized completo: cada campo de raíz que muestra es requerido para ese evento, salvo provider y metadata, que son opcionales y llegan a la orden como authority.providerIdentity y authority.providerMetadata — ver Qué es requerido, por evento.

El estado fiscal_graphic

fiscal_graphic reporta que tu proveedor está entregando la representación gráfica — lo que imprime la caja para el 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.
  • Requiere document y un graphic no vacío. La representación gráfica es lo que imprime la caja — el QR, el TED, la clave de acceso codificada — y viaja en el graphic de la raíz. El PDF y el XML son los archivos del documento autorizado y llegan con authorized.
  • 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.
fiscal_graphic (EC)

Resultados negativos (rejected, denied, error)

Para estados no autorizados, omite document (o mándalo en null) y provee failure. El country sigue aplicando (valida el routing); el documento no se requiere porque no hay artefacto autorizado — y documentType/docSubtype tampoco, porque no hay comprobante que tipificar. Un resultado negativo no guarda datos de comprobante. Si mandas documentNumber, providerDocId, issuedAt, authorizedAt, importes, pdfUrl, xmlUrl o graphic con rejected, denied o error, Fire los ignora: solo se guardan documentType, docSubtype, las claves del país en document, failure, provider y metadata. Un documento que ya tenía datos de comprobante los conserva.
Rechazado

Reintentos y anulación rechazada

Una emisión fallida se puede reintentar. Si reportaste rejected, denied o error y después reintentás ante el ente, mandá el nuevo resultado con el mismo eventId — el de la emisión. Fire lo acepta y el flujo sigue normal: si el nuevo resultado es authorized, se despacha order.invoiced. Si el reintento vuelve a fallar, el motivo nuevo reemplaza al anterior.
Anulación rechazada por el ente. Cuando Fire te pide anular una factura autorizada (evento order.cancelled), la orden queda “anulando”. Si el ente no acepta la anulación, mandá rejected, denied o error con el eventId de esa anulación y el motivo en failure. Fire lo interpreta como anulación fallida: la factura sigue vigente (la orden vuelve a authorized, con el motivo en error) y no se despacha ningún evento. Si después reintentás y el ente la acepta, mandá cancelled con el mismo eventId y se despacha order.reversed.
Un rejected sobre una factura autorizada que no se está anulando sigue significando lo mismo que siempre: el ente rechazó la factura.

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.
boolean
Siempre true cuando la petición fue aceptada y encolada.
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. Un eventType distinto sobre el mismo par nunca llega a este campo: devuelve 409, salvo fiscal_graphic.
string
Echo del eventId que enviaste (el event.id de la emisión de Fire). Usalo para casar este acuse con tu envío.
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.
string
Status actual del registro en la cola — queued → processing → processed (y retry / failed / dead / ignored).
string
Timestamp ISO 8601 UTC de cuándo Fire recibió por primera vez este evento. Estable entre reintentos — útil como ancla de traza.
string
Resumen legible de lo que pasó.

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:
string
Ciclo de vida en la cola: queued → processing → processed (listo) · failed / dead (se agotaron los reintentos) · retry (esperando el próximo intento) · ignored.
number
Intentos de procesamiento hasta ahora.
object | null
En éxito, el resultado del worker — ej. { "kind": "updated", "documentId": "…", "receiptId": "…" }.
object | null
{ "message": "…" } cuando el último intento falló; null si no.
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). El mismo par reenviado con el mismo eventType es un replay benigno; el mismo par con un eventType distinto devuelve 409 — un intento de colgar una acción sobre el evento de otra — salvo fiscal_graphic, que puede compartir el eventId con su resultado. 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:
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. Cuando la orden ya tiene documento, el callback se asocia a él por eventId. Un callback con otro eventId válido termina en notFound, no en regression; reenviar el eventId del documento con un estado anterior es un reenvío del mismo par. Ninguno de los dos cambia el documento.

Qué pasa después del 202

Del callback a los eventos

Cómo llega cada campo que mandás acá a order.invoiced y order.reversed.

Panorama fiscal

Numeración, callback y eventos en una sola imagen.
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. Se actualiza el documento fiscal con el nuevo status y los identificadores del ente.
  2. El veredicto se guarda en la orden, estructurado: campos canónicos, amounts, countryData y graphic — y se agrega al historial fiscal de la orden.
  3. Se despacha un evento outbound, para todos los países: order.invoiced para eventType=authorized, order.reversed para eventType=cancelled. Los Integration Flows activos para ese evento corren y hacen POST a tu endpoint. fiscal_graphic, rejected, denied y error actualizan el estado pero no emiten evento.

Del callback a los eventos

Quien consume order.invoiced u order.reversed lee exactamente lo que mandaste acá, reorganizado en el bloque fiscal del evento. La referencia completa de ese bloque está en order.invoiced. Los campos raíz caen en fiscal.authority (o cancellation.fiscal.authority) con los mismos nombres, salvo los tres importes, que se agrupan en authority.amounts. document pasa a ser authority.countryData, y provider y metadata pasan a ser providerIdentity y providerMetadata (null si están ausentes o vacíos). La tabla campo por campo está en Del callback a los eventos. document y countryData son el mismo bloque. Fire no guarda una lista de las claves de cada país: todo lo que venga en document que no sea un campo canónico llega intacto a countryData. Un identificador nuevo que tu ente empiece a exigir viaja a los eventos sin ningún cambio del lado de Fire.

Por qué este endpoint conserva su path /v1

Los renombres de campos de arriba (docType → documentType, countryCode → country, error → failure, providerSpecific → metadata) rompen a un emisor que siga usando los nombres anteriores: recibe 400 nombrando el campo. Aun así, el endpoint conservó su path. Cuando cambió el contrato, este callback solo llevaba tráfico de pruebas en producción, y el cliente de pruebas de Fire se movió en el mismo release, así que un /v2 paralelo habría mantenido dos formas de decir lo mismo para ningún emisor. Del lado de los eventos es distinto — tienen consumidores en producción — y los tres eventos afectados publicaron cada uno una nueva versión documentada.

Relacionado

order.completed

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

order.invoiced

Evento outbound disparado por un callback authorized, para todos los países.

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.