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 '
{
"country": "<string>",
"eventType": "<string>",
"providerEventId": "<string>",
"occurredAt": "<string>",
"orderId": "<string>",
"eventId": "<string>",
"document": {},
"documentType": "<string>",
"docSubtype": "<string>",
"documentNumber": "<string>",
"issuedAt": "<string>",
"authorizedAt": "<string>",
"cancelledAt": "<string>",
"authorizationMode": "<string>",
"pdfUrl": "<string>",
"xmlUrl": "<string>",
"totalAmount": 123,
"taxAmount": 123,
"currencyCode": "<string>",
"providerDocId": "<string>",
"graphic": {},
"provider": {
"name": "<string>",
"version": "<string>",
"reference": "<string>"
},
"failure": {
"code": "<string>",
"scope": "<string>",
"message": "<string>",
"details": [
{}
]
},
"metadata": {}
}
'import requests
url = "https://app.fire.rest/api/v1/webhooks/fiscal/callback"
payload = {
"country": "<string>",
"eventType": "<string>",
"providerEventId": "<string>",
"occurredAt": "<string>",
"orderId": "<string>",
"eventId": "<string>",
"document": {},
"documentType": "<string>",
"docSubtype": "<string>",
"documentNumber": "<string>",
"issuedAt": "<string>",
"authorizedAt": "<string>",
"cancelledAt": "<string>",
"authorizationMode": "<string>",
"pdfUrl": "<string>",
"xmlUrl": "<string>",
"totalAmount": 123,
"taxAmount": 123,
"currencyCode": "<string>",
"providerDocId": "<string>",
"graphic": {},
"provider": {
"name": "<string>",
"version": "<string>",
"reference": "<string>"
},
"failure": {
"code": "<string>",
"scope": "<string>",
"message": "<string>",
"details": [{}]
},
"metadata": {}
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
country: '<string>',
eventType: '<string>',
providerEventId: '<string>',
occurredAt: '<string>',
orderId: '<string>',
eventId: '<string>',
document: {},
documentType: '<string>',
docSubtype: '<string>',
documentNumber: '<string>',
issuedAt: '<string>',
authorizedAt: '<string>',
cancelledAt: '<string>',
authorizationMode: '<string>',
pdfUrl: '<string>',
xmlUrl: '<string>',
totalAmount: 123,
taxAmount: 123,
currencyCode: '<string>',
providerDocId: '<string>',
graphic: {},
provider: {name: '<string>', version: '<string>', reference: '<string>'},
failure: {code: '<string>', scope: '<string>', message: '<string>', details: [{}]},
metadata: {}
})
};
fetch('https://app.fire.rest/api/v1/webhooks/fiscal/callback', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.fire.rest/api/v1/webhooks/fiscal/callback",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'country' => '<string>',
'eventType' => '<string>',
'providerEventId' => '<string>',
'occurredAt' => '<string>',
'orderId' => '<string>',
'eventId' => '<string>',
'document' => [
],
'documentType' => '<string>',
'docSubtype' => '<string>',
'documentNumber' => '<string>',
'issuedAt' => '<string>',
'authorizedAt' => '<string>',
'cancelledAt' => '<string>',
'authorizationMode' => '<string>',
'pdfUrl' => '<string>',
'xmlUrl' => '<string>',
'totalAmount' => 123,
'taxAmount' => 123,
'currencyCode' => '<string>',
'providerDocId' => '<string>',
'graphic' => [
],
'provider' => [
'name' => '<string>',
'version' => '<string>',
'reference' => '<string>'
],
'failure' => [
'code' => '<string>',
'scope' => '<string>',
'message' => '<string>',
'details' => [
[
]
]
],
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.fire.rest/api/v1/webhooks/fiscal/callback"
payload := strings.NewReader("{\n \"country\": \"<string>\",\n \"eventType\": \"<string>\",\n \"providerEventId\": \"<string>\",\n \"occurredAt\": \"<string>\",\n \"orderId\": \"<string>\",\n \"eventId\": \"<string>\",\n \"document\": {},\n \"documentType\": \"<string>\",\n \"docSubtype\": \"<string>\",\n \"documentNumber\": \"<string>\",\n \"issuedAt\": \"<string>\",\n \"authorizedAt\": \"<string>\",\n \"cancelledAt\": \"<string>\",\n \"authorizationMode\": \"<string>\",\n \"pdfUrl\": \"<string>\",\n \"xmlUrl\": \"<string>\",\n \"totalAmount\": 123,\n \"taxAmount\": 123,\n \"currencyCode\": \"<string>\",\n \"providerDocId\": \"<string>\",\n \"graphic\": {},\n \"provider\": {\n \"name\": \"<string>\",\n \"version\": \"<string>\",\n \"reference\": \"<string>\"\n },\n \"failure\": {\n \"code\": \"<string>\",\n \"scope\": \"<string>\",\n \"message\": \"<string>\",\n \"details\": [\n {}\n ]\n },\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.fire.rest/api/v1/webhooks/fiscal/callback")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"country\": \"<string>\",\n \"eventType\": \"<string>\",\n \"providerEventId\": \"<string>\",\n \"occurredAt\": \"<string>\",\n \"orderId\": \"<string>\",\n \"eventId\": \"<string>\",\n \"document\": {},\n \"documentType\": \"<string>\",\n \"docSubtype\": \"<string>\",\n \"documentNumber\": \"<string>\",\n \"issuedAt\": \"<string>\",\n \"authorizedAt\": \"<string>\",\n \"cancelledAt\": \"<string>\",\n \"authorizationMode\": \"<string>\",\n \"pdfUrl\": \"<string>\",\n \"xmlUrl\": \"<string>\",\n \"totalAmount\": 123,\n \"taxAmount\": 123,\n \"currencyCode\": \"<string>\",\n \"providerDocId\": \"<string>\",\n \"graphic\": {},\n \"provider\": {\n \"name\": \"<string>\",\n \"version\": \"<string>\",\n \"reference\": \"<string>\"\n },\n \"failure\": {\n \"code\": \"<string>\",\n \"scope\": \"<string>\",\n \"message\": \"<string>\",\n \"details\": [\n {}\n ]\n },\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.fire.rest/api/v1/webhooks/fiscal/callback")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"country\": \"<string>\",\n \"eventType\": \"<string>\",\n \"providerEventId\": \"<string>\",\n \"occurredAt\": \"<string>\",\n \"orderId\": \"<string>\",\n \"eventId\": \"<string>\",\n \"document\": {},\n \"documentType\": \"<string>\",\n \"docSubtype\": \"<string>\",\n \"documentNumber\": \"<string>\",\n \"issuedAt\": \"<string>\",\n \"authorizedAt\": \"<string>\",\n \"cancelledAt\": \"<string>\",\n \"authorizationMode\": \"<string>\",\n \"pdfUrl\": \"<string>\",\n \"xmlUrl\": \"<string>\",\n \"totalAmount\": 123,\n \"taxAmount\": 123,\n \"currencyCode\": \"<string>\",\n \"providerDocId\": \"<string>\",\n \"graphic\": {},\n \"provider\": {\n \"name\": \"<string>\",\n \"version\": \"<string>\",\n \"reference\": \"<string>\"\n },\n \"failure\": {\n \"code\": \"<string>\",\n \"scope\": \"<string>\",\n \"message\": \"<string>\",\n \"details\": [\n {}\n ]\n },\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_body{
"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."
}
{
"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."
}
{
"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."
}
{
"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."
}
{
"success": false,
"error": "UNAUTHORIZED",
"message": "API key required. Use x-api-key: pk_live_... header"
}
{
"success": false,
"error": "FORBIDDEN",
"message": "webhooks:fiscal requires a vendor-scoped API key (account + vendor binding). Generate one from /developers/firepos-api-management."
}
{
"success": false,
"error": "SERVICE_UNAVAILABLE",
"message": "API key verification is temporarily unavailable (auth store unreachable). Retry the request."
}
API
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 dispara order.invoiced / order.reversed para todos los países.
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 '
{
"country": "<string>",
"eventType": "<string>",
"providerEventId": "<string>",
"occurredAt": "<string>",
"orderId": "<string>",
"eventId": "<string>",
"document": {},
"documentType": "<string>",
"docSubtype": "<string>",
"documentNumber": "<string>",
"issuedAt": "<string>",
"authorizedAt": "<string>",
"cancelledAt": "<string>",
"authorizationMode": "<string>",
"pdfUrl": "<string>",
"xmlUrl": "<string>",
"totalAmount": 123,
"taxAmount": 123,
"currencyCode": "<string>",
"providerDocId": "<string>",
"graphic": {},
"provider": {
"name": "<string>",
"version": "<string>",
"reference": "<string>"
},
"failure": {
"code": "<string>",
"scope": "<string>",
"message": "<string>",
"details": [
{}
]
},
"metadata": {}
}
'import requests
url = "https://app.fire.rest/api/v1/webhooks/fiscal/callback"
payload = {
"country": "<string>",
"eventType": "<string>",
"providerEventId": "<string>",
"occurredAt": "<string>",
"orderId": "<string>",
"eventId": "<string>",
"document": {},
"documentType": "<string>",
"docSubtype": "<string>",
"documentNumber": "<string>",
"issuedAt": "<string>",
"authorizedAt": "<string>",
"cancelledAt": "<string>",
"authorizationMode": "<string>",
"pdfUrl": "<string>",
"xmlUrl": "<string>",
"totalAmount": 123,
"taxAmount": 123,
"currencyCode": "<string>",
"providerDocId": "<string>",
"graphic": {},
"provider": {
"name": "<string>",
"version": "<string>",
"reference": "<string>"
},
"failure": {
"code": "<string>",
"scope": "<string>",
"message": "<string>",
"details": [{}]
},
"metadata": {}
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
country: '<string>',
eventType: '<string>',
providerEventId: '<string>',
occurredAt: '<string>',
orderId: '<string>',
eventId: '<string>',
document: {},
documentType: '<string>',
docSubtype: '<string>',
documentNumber: '<string>',
issuedAt: '<string>',
authorizedAt: '<string>',
cancelledAt: '<string>',
authorizationMode: '<string>',
pdfUrl: '<string>',
xmlUrl: '<string>',
totalAmount: 123,
taxAmount: 123,
currencyCode: '<string>',
providerDocId: '<string>',
graphic: {},
provider: {name: '<string>', version: '<string>', reference: '<string>'},
failure: {code: '<string>', scope: '<string>', message: '<string>', details: [{}]},
metadata: {}
})
};
fetch('https://app.fire.rest/api/v1/webhooks/fiscal/callback', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.fire.rest/api/v1/webhooks/fiscal/callback",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'country' => '<string>',
'eventType' => '<string>',
'providerEventId' => '<string>',
'occurredAt' => '<string>',
'orderId' => '<string>',
'eventId' => '<string>',
'document' => [
],
'documentType' => '<string>',
'docSubtype' => '<string>',
'documentNumber' => '<string>',
'issuedAt' => '<string>',
'authorizedAt' => '<string>',
'cancelledAt' => '<string>',
'authorizationMode' => '<string>',
'pdfUrl' => '<string>',
'xmlUrl' => '<string>',
'totalAmount' => 123,
'taxAmount' => 123,
'currencyCode' => '<string>',
'providerDocId' => '<string>',
'graphic' => [
],
'provider' => [
'name' => '<string>',
'version' => '<string>',
'reference' => '<string>'
],
'failure' => [
'code' => '<string>',
'scope' => '<string>',
'message' => '<string>',
'details' => [
[
]
]
],
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.fire.rest/api/v1/webhooks/fiscal/callback"
payload := strings.NewReader("{\n \"country\": \"<string>\",\n \"eventType\": \"<string>\",\n \"providerEventId\": \"<string>\",\n \"occurredAt\": \"<string>\",\n \"orderId\": \"<string>\",\n \"eventId\": \"<string>\",\n \"document\": {},\n \"documentType\": \"<string>\",\n \"docSubtype\": \"<string>\",\n \"documentNumber\": \"<string>\",\n \"issuedAt\": \"<string>\",\n \"authorizedAt\": \"<string>\",\n \"cancelledAt\": \"<string>\",\n \"authorizationMode\": \"<string>\",\n \"pdfUrl\": \"<string>\",\n \"xmlUrl\": \"<string>\",\n \"totalAmount\": 123,\n \"taxAmount\": 123,\n \"currencyCode\": \"<string>\",\n \"providerDocId\": \"<string>\",\n \"graphic\": {},\n \"provider\": {\n \"name\": \"<string>\",\n \"version\": \"<string>\",\n \"reference\": \"<string>\"\n },\n \"failure\": {\n \"code\": \"<string>\",\n \"scope\": \"<string>\",\n \"message\": \"<string>\",\n \"details\": [\n {}\n ]\n },\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.fire.rest/api/v1/webhooks/fiscal/callback")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"country\": \"<string>\",\n \"eventType\": \"<string>\",\n \"providerEventId\": \"<string>\",\n \"occurredAt\": \"<string>\",\n \"orderId\": \"<string>\",\n \"eventId\": \"<string>\",\n \"document\": {},\n \"documentType\": \"<string>\",\n \"docSubtype\": \"<string>\",\n \"documentNumber\": \"<string>\",\n \"issuedAt\": \"<string>\",\n \"authorizedAt\": \"<string>\",\n \"cancelledAt\": \"<string>\",\n \"authorizationMode\": \"<string>\",\n \"pdfUrl\": \"<string>\",\n \"xmlUrl\": \"<string>\",\n \"totalAmount\": 123,\n \"taxAmount\": 123,\n \"currencyCode\": \"<string>\",\n \"providerDocId\": \"<string>\",\n \"graphic\": {},\n \"provider\": {\n \"name\": \"<string>\",\n \"version\": \"<string>\",\n \"reference\": \"<string>\"\n },\n \"failure\": {\n \"code\": \"<string>\",\n \"scope\": \"<string>\",\n \"message\": \"<string>\",\n \"details\": [\n {}\n ]\n },\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.fire.rest/api/v1/webhooks/fiscal/callback")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"country\": \"<string>\",\n \"eventType\": \"<string>\",\n \"providerEventId\": \"<string>\",\n \"occurredAt\": \"<string>\",\n \"orderId\": \"<string>\",\n \"eventId\": \"<string>\",\n \"document\": {},\n \"documentType\": \"<string>\",\n \"docSubtype\": \"<string>\",\n \"documentNumber\": \"<string>\",\n \"issuedAt\": \"<string>\",\n \"authorizedAt\": \"<string>\",\n \"cancelledAt\": \"<string>\",\n \"authorizationMode\": \"<string>\",\n \"pdfUrl\": \"<string>\",\n \"xmlUrl\": \"<string>\",\n \"totalAmount\": 123,\n \"taxAmount\": 123,\n \"currencyCode\": \"<string>\",\n \"providerDocId\": \"<string>\",\n \"graphic\": {},\n \"provider\": {\n \"name\": \"<string>\",\n \"version\": \"<string>\",\n \"reference\": \"<string>\"\n },\n \"failure\": {\n \"code\": \"<string>\",\n \"scope\": \"<string>\",\n \"message\": \"<string>\",\n \"details\": [\n {}\n ]\n },\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_body{
"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."
}
{
"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."
}
{
"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."
}
{
"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."
}
{
"success": false,
"error": "UNAUTHORIZED",
"message": "API key required. Use x-api-key: pk_live_... header"
}
{
"success": false,
"error": "FORBIDDEN",
"message": "webhooks:fiscal requires a vendor-scoped API key (account + vendor binding). Generate one from /developers/firepos-api-management."
}
{
"success": false,
"error": "SERVICE_UNAVAILABLE",
"message": "API key verification is temporarily unavailable (auth store unreachable). Retry the request."
}
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
Cómo complementa
Cómo llama cada régimen a su documento va en
El estado
Resultados negativos (
Para estados no autorizados, omite
Anulación rechazada por el ente. Cuando Fire te pide anular una factura autorizada (evento
Un
El Por qué este endpoint conserva su path
Los renombres de campos de arriba (
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 scopewebhooks: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 estadofiscal_graphicabajo.authorized— la autoridad fiscal aprobó el documentocancelled— un documento previamente autorizado fue canceladorejected— 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
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
eventIdque no referencie un evento de Fire para esa orden se rechaza con400antes del202. - Cada acción lleva su propio evento. Ecoa el
event.iddel evento que disparó esta acción — la emisiónorder.invoicedpara un callbackauthorized, el evento de cancelación/reverso para un callbackcancelled. Reusar eleventIdde una acción para otroeventType(ej. uncancelledque ecoa eleventIddelauthorized) se rechaza con409— 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 dedocument. 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 endocumentType. No hay un tercer tipo: cualquier otro valor devuelve 400.
documentType | Qué es | eventType del callback | Operación de numeración |
|---|---|---|---|
SALE_INVOICE | El documento de la venta: factura, NFC-e / NF-e, boleta… | authorized | INVOICE |
CREDIT_NOTE | El documento que anula una venta | cancelled | CANCEL |
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.Mostrar failure
Mostrar failure
string
Código de error opcional del proveedor/autoridad (p. ej.
DIAN_42, cStat_204).string
TECHNICAL o FUNCTIONAL. Es lo que el canal ramifica: con TECHNICAL el comprobante todavía puede salir y el ticket imprime «en trámite»; con FUNCTIONAL el ente lo rechazó por su contenido y no va a salir.string
requerido
Mensaje legible. Mínimo 1 carácter.
array
Detalle campo por campo, cuando el ente lo da: una lista de
{ "field": "…", "issue": "…" }, ambos opcionales.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
| Campo | authorized | cancelled | fiscal_graphic | rejected · denied · error |
|---|---|---|---|---|
document | requerido | requerido | requerido | — |
documentType | SALE_INVOICE | CREDIT_NOTE | — | — |
docSubtype | requerido | requerido | — | — |
pdfUrl, xmlUrl | requerido | requerido | — | — |
documentNumber, providerDocId, issuedAt | requerido | — | — | — |
totalAmount, taxAmount, currencyCode | requerido | — | — | — |
graphic | opcional | opcional | requerido, no vacío | — |
failure | — | — | — | requerido |
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 campodocument 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.- Brasil (BR)
- Colombia (CO)
- Ecuador (EC)
- Chile (CL)
- Argentina (AR)
- Venezuela (VE)
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 (
El ejemplo de abajo es un payload
documentType, docSubtype, documentNumber, pdfUrl, xmlUrl, issuedAt, 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 | |
cStat | int / string | null | Código de estado de SEFAZ: 100 autorizado, 135 cancelación registrada, 865 rechazado. Llega a los eventos como authority.countryData.cStat |
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.{
"country": "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",
"documentType": "SALE_INVOICE",
"docSubtype": "nfce",
"documentNumber": "1",
"issuedAt": "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",
"providerDocId": "69fa97fe427d1240856e1282",
"provider": { "name": "fiscal-provider", "version": "2026.09.1", "reference": "REF-69fa97fe427d1240856e1282" },
"metadata": { "providerTraceId": "trace-69fa97fe427d1240856e1282" },
"document": {
"chaveAcesso": "35260229062609000177650500000000011000000010",
"protocolo": "141210001176277",
"numero": 1,
"serie": 50,
"modelo": 65,
"cStat": 100
}
}
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 |
{
"country": "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",
"documentType": "SALE_INVOICE",
"docSubtype": "factura",
"documentNumber": "C012001",
"issuedAt": "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",
"providerDocId": "C012001",
"provider": { "name": "fiscal-provider", "version": "2026.09.1", "reference": "REF-C012001" },
"metadata": { "providerTraceId": "trace-C012001" },
"document": {
"cufe": "633732c7a2a577bfa1828551e64d03f715f0",
"prefijo": "C012",
"numeroDian": "001",
"numeroComprobante": "C012001",
"ambiente": "1",
"qrCode": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=633732c7a2a577bfa1828551e64d03f715f0"
}
}
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 son opcionales dentro de document: quien ya manda callbacks sin ellos sigue
funcionando. Eso no se extiende a la raíz: los campos listados en Qué es requerido, por evento
son requeridos, y un callback al que le falten recibe 400.Documentos fiscales ecuatorianos (SRI).
| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
claveAcceso | string | ✓ | Exactamente 49 dígitos — clave de acceso SRI |
numeroComprobante | string | null | El número visible, ya armado: estab-ptoEmi-secuencial | |
ambiente | '1' / '2' | null | 1 = pruebas, 2 = producción | |
establecimiento | string | null | Código del establecimiento (001) | |
puntoEmision | string | null | Código del punto de emisión (020) | |
secuencial | string | null | Secuencial del comprobante (000000123) |
numeroAutorizacion fue retirado del contrato. En el esquema de autorización en
línea el SRI devuelve como número de autorización la misma clave de acceso, así que
era el mismo dato declarado dos veces. Si tu sistema lo sigue enviando no rompe nada
—los campos no declarados se preservan— pero no lo pedimos ni lo interpretamos.
Quien necesite el número de autorización lee claveAcceso.Las tres piezas del número —establecimiento, puntoEmision, secuencial— ahora
existen en las dos rutas: la numeración previa y este callback. Son opcionales: si
tu proveedor no las emite, no las mandes. Fire no las deriva de claveAcceso —esa
regla es del régimen— y el comprobante se imprime sin ellas.{
"country": "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",
"documentType": "SALE_INVOICE",
"docSubtype": "factura",
"documentNumber": "001-020-000000123",
"issuedAt": "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",
"providerDocId": "AUT-EC-001",
"provider": { "name": "fiscal-provider", "version": "2026.09.1", "reference": "REF-AUT-EC-001" },
"metadata": { "providerTraceId": "trace-AUT-EC-001" },
"document": {
"numeroComprobante": "001-020-000000123",
"claveAcceso": "0102030405060708091011121314151617181920212223242",
"establecimiento": "001",
"puntoEmision": "020",
"secuencial": "000000123",
"ambiente": "2"
}
}
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 |
{
"country": "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",
"documentType": "SALE_INVOICE",
"docSubtype": "factura",
"documentNumber": "12345",
"issuedAt": "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",
"providerDocId": "12345",
"provider": { "name": "fiscal-provider", "version": "2026.09.1", "reference": "REF-12345" },
"metadata": { "providerTraceId": "trace-12345" },
"document": {
"folio": 12345,
"ted": "<TED>...base64-or-xml...</TED>",
"tipoDte": 33,
"trackId": "SII-TRK-998877"
}
}
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, …) |
{
"country": "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",
"documentType": "SALE_INVOICE",
"docSubtype": "factura_a",
"documentNumber": "0001-00012345",
"issuedAt": "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",
"providerDocId": "0001-12345",
"provider": { "name": "fiscal-provider", "version": "2026.09.1", "reference": "REF-0001-12345" },
"metadata": { "providerTraceId": "trace-0001-12345" },
"document": {
"cae": "74256178925412",
"fechaVtoCae": "2026-05-31",
"puntoVenta": 1,
"numeroComprobante": 12345,
"tipoComprobante": 1
}
}
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 |
{
"country": "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",
"documentType": "SALE_INVOICE",
"docSubtype": "factura",
"documentNumber": "12345",
"issuedAt": "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",
"providerDocId": "00-00012345",
"provider": { "name": "fiscal-provider", "version": "2026.09.1", "reference": "REF-00-00012345" },
"metadata": { "providerTraceId": "trace-00-00012345" },
"document": {
"numeroControl": "00-00012345",
"numeroFactura": "12345",
"rifEmisor": "J-12345678-9"
}
}
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_graphicno 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 unfiscal_graphicprevio. Ambos flujos son válidos. - Puede compartir el
eventIdcon su desenlace. La gráfica y el resultado terminal pertenecen a la misma acción, así que podés enviarfiscal_graphicy luegoauthorized/rejected/denied/cancelledreusando el mismoeventId. Eso no es un conflicto y nunca devuelve409— ver Idempotencia y escenarios. - Requiere
documenty ungraphicno 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 elgraphicde la raíz. El PDF y el XML son los archivos del documento autorizado y llegan conauthorized. - No dispara ningún evento saliente.
order.invoicedyorder.reversedsiguen atados aauthorizedycancelledrespectivamente. La gráfica solo cambia el estado del documento.
fiscal_graphic (EC)
{
"country": "EC",
"eventType": "fiscal_graphic",
"providerEventId": "evt-ec-graphic-1",
"occurredAt": "2026-04-26T14:32:05.000Z",
"orderId": "550e8400-e29b-41d4-a716-446655440000",
"eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"documentType": "SALE_INVOICE",
"docSubtype": "factura",
"documentNumber": "001-020-000000123",
"issuedAt": "2026-04-26T14:32:04.000Z",
"providerDocId": "AUT-EC-001",
"document": {
"numeroComprobante": "001-020-000000123",
"claveAcceso": "0102030405060708091011121314151617181920212223242",
"establecimiento": "001",
"puntoEmision": "020",
"secuencial": "000000123",
"ambiente": "2"
},
"graphic": {
"qr": "0102030405060708091011121314151617181920212223242"
},
"provider": {
"name": "hio.fiscalization",
"version": "2026.09.1",
"reference": "HIO-EC-GRAPHIC-001"
},
"metadata": {
"providerTraceId": "ec-graphic-trace-001"
}
}
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
{
"country": "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,
"failure": {
"code": "DIAN_42",
"message": "CUFE inválido"
},
"provider": { "name": "fiscal-provider", "version": "2026.09.1", "reference": "REF-CO-REJ-002" },
"metadata": { "providerTraceId": "trace-co-rej-002" }
}
Reintentos y anulación rechazada
Una emisión fallida se puede reintentar. Si reportasterejected, 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.
rejected (eventId E1) → authorized (eventId E1) ✓ se aplica, sale order.invoiced
rejected (eventId E1) → error (eventId E1) ✓ se guarda el motivo nuevo
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.
order.cancelled (eventId C1) → rejected (C1) → la factura sigue autorizada
→ cancelled (C1) → anulada, sale 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 devuelve202 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ó.
{
"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."
}
{
"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."
}
{
"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."
}
{
"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."
}
{
"success": false,
"error": "UNAUTHORIZED",
"message": "API key required. Use x-api-key: pk_live_... header"
}
{
"success": false,
"error": "FORBIDDEN",
"message": "webhooks:fiscal requires a vendor-scoped API key (account + vendor binding). Generate one from /developers/firepos-api-management."
}
{
"success": false,
"error": "SERVICE_UNAVAILABLE",
"message": "API key verification is temporarily unavailable (auth store unreachable). Retry the request."
}
Comprobar el resultado
Como el procesamiento es asíncrono, el202 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>
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.{
"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
}
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:
| Escenario | Resultado |
|---|---|
(orderId, eventId) nuevo | 202 · duplicate: false — encolado |
Mismo (orderId, eventId) con el mismo 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 |
Un fallo (rejected, denied o error) y luego otro desenlace con el mismo eventId (ej. authorized tras reintentar) | 202 — es el reintento de la misma emisión, se aplica |
El mismo fallo reenviado con el mismo eventId (ej. un segundo rejected con otro motivo) | 202 · duplicate: false — se procesa y se guarda el motivo nuevo. Un fallo no dispara eventos, así que reprocesarlo es inocuo |
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á |
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 enfiscal_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.
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:
- Se actualiza el documento fiscal con el nuevo status y los identificadores del ente.
- El veredicto se guarda en la orden, estructurado: campos canónicos,
amounts,countryDataygraphic— y se agrega al historial fiscal de la orden. - Se despacha un evento outbound, para todos los países:
order.invoicedparaeventType=authorized,order.reversedparaeventType=cancelled. Los Integration Flows activos para ese evento corren y hacen POST a tu endpoint.fiscal_graphic,rejected,deniedyerroractualizan el estado pero no emiten evento.
Del callback a los eventos
Quien consumeorder.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.

