Fiscal callback
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
Fiscal callback
Inbound endpoint your fiscal provider POSTs to when a fiscal document changes state. Multi-country (BR, CO, EC, CL, AR, VE). Updates the order’s fiscal state inside Fire and triggers order.invoiced / order.reversed for every country.
POST
/
api
/
v1
/
webhooks
/
fiscal
/
callback
Fiscal callback
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."
}
This endpoint is inbound — your fiscal provider POSTs to it whenever a fiscal document is authorized, rejected, denied, cancelled, or errors out. It is the canonical, multi-country fiscal callback API that accepts payloads from BR, CO, EC, CL, AR, VE. Fire validates the payload, stores the authority’s verdict on the order, and dispatches
How it complements
What each regime calls its document goes in
The
Negative outcomes (
For non-authorized states, omit
Cancellation rejected by the authority. When Fire asks you to void an authorized invoice (
A
The Why this endpoint keeps its
The field renames above (
order.invoiced / order.reversed to your Integration Flows — for every country. What you send here is what the event consumers read: see From the callback to the events.
This endpoint is asynchronous. Fire authenticates, runs the source-of-truth + tenant guard, deduplicates, pre-marks the order as
processing, and enqueues the callback — then returns 202 Accepted with a webhookEventId (typically in under 100 ms). The fiscal_documents / orders.fiscal update happens a moment later in a background worker (usually within ~2 seconds). To check the processing outcome, poll GET /v1/webhooks/events/{webhookEventId}. Client-fixable problems (bad payload, wrong eventId, wrong tenant, action reusing another event’s id) are rejected synchronously with 4xx before the 202.How it complements order.completed and order.cancelled
order.completed and order.cancelled carry an order’s business state. The fiscal callback carries the fiscal state (SEFAZ / DIAN / SRI / SII / AFIP / SENIAT authorization). Together they form the lifecycle below:
After an authorized callback is processed, Fire emits order.invoiced; after a cancelled one, order.reversed. Both fire for every country — the country travels in the event’s fiscal block, not in its name. Later order events for the same orderId also reflect the new status in data.lastKnown.fiscal.
For the whole fiscal picture — numbering, callback and events, and how they fit together — see the fiscal overview.
Authentication
This endpoint requires an API key with thewebhooks:fiscal scope, vendor-scoped to the account that owns the order. Unscoped or account-only keys are rejected with 403 Forbidden.
string
required
Your Fire API key. Generate one from the dashboard at Developers → API Management, with scope
webhooks:fiscal and a vendor binding for the account whose orders this key may update.string
Legacy alias: the same Fire API key, as
Bearer pk_live_.... Prefer x-api-key; if both are sent, x-api-key is used.Request body
The body is a JSON object with these top-level fields. Everything canonical lives up here;document carries ONLY your country authority vocabulary, picked by country — see Document by country below.
string
required
ISO 3166-1 alpha-2 country code. One of
BR, CO, EC, CL, AR, VE. Determines the validation rules for the document object.Formerly named countryCode.string
required
Document state. One of:
fiscal_graphic— the provider is delivering the representación gráfica (the printable artifact) for the document. It is not an approval and not a terminal state — see Thefiscal_graphicstate below.authorized— the fiscal authority approved the documentcancelled— a previously authorized document was cancelledrejected— the document was rejected (validation, schema, signature)denied— the authority denied the request (typically a permanent business rule failure)error— a non-recoverable error occurred at the provider or authority
eventType is fiscal_graphic, authorized or cancelled, the document field is required. When it is rejected, denied, or error, the failure field is required instead.string
required
Your provider’s own id for this delivery. Stored for audit/forensics — it is not the idempotency key. Fire deduplicates on
(orderId, eventId) — a different eventType on the same pair returns 409, except fiscal_graphic and retries after a failure (see Retries and rejected cancellations) — so you may safely send a fresh providerEventId on every retry without creating duplicates. Use the provider’s native event id if available; otherwise a UUID.string
required
ISO 8601 UTC timestamp of when the event happened at the fiscal authority (not when the provider sent the callback).
string
required
UUID of the order in Fire. Matches
data.orderId in order.completed and order.cancelled events. Fire uses this to find the existing fiscal document.string
required
Correlation UUID and idempotency key (together with
orderId). It must be the event.id of a V4 envelope Fire emitted for this order — echo it exactly; never invent it.- Source-of-truth check. An
eventIdthat does not reference a Fire event for that order is rejected with400before the202. - Each action carries its own event. Echo the
event.idof the event that drove this action — theorder.invoicedemission for anauthorizedcallback, the cancellation/reversal event for acancelledcallback. Reusing one action’seventIdfor a different terminaleventType(e.g. acancelledthat echoes theauthorized’seventId) is rejected with409— a cancellation must reference its own event, not ride on the authorization’s. See Idempotency & scenarios.
object
Your country authority vocabulary, and nothing else:
claveAcceso, cufe, chaveAcesso, folio, cae… Required when eventType is authorized, cancelled or fiscal_graphic. See Document by country.The common fields —documentType, the number, the URLs, the amounts, the dates— do not live in here: they are at the root. Everything in document reaches the events as authority.countryData, with the same key names.Canonical fields (root)
They mean the same thing in all six countries, so they live at the root of the payload rather than insidedocument. Same rule as the fiscal numbering response, so the same fact is never looked up at two different depths depending on which path it came in through.
string
SALE_INVOICE or CREDIT_NOTE — the same vocabulary as the fiscal numbering. Required when there is a document (authorized / cancelled), and it must match the event: authorized is a SALE_INVOICE, cancelled is a CREDIT_NOTE. Any other combination returns 400.Formerly docType: "invoice" | "cancellation". The old name is ignored, not translated: an authorized or cancelled payload that carries only docType receives 400 on documentType.string
What your regime calls it:
nfce, nfe, factura, boleta, factura_a. Open: we do not validate it against a list and it branches nothing on our side — it is referential. Required when there is a document.Document types
Fire accepts exactly two values indocumentType. There is no third type: any other value returns 400.
documentType | What it is | Callback eventType | Numbering operation |
|---|---|---|---|
SALE_INVOICE | The sale document: invoice, NFC-e / NF-e, boleta… | authorized | INVOICE |
CREDIT_NOTE | The document that annuls a sale | cancelled | CANCEL |
docSubtype, and that one is open: nfce, nfe (Brazil), factura (Ecuador, Colombia), boleta (Chile), factura_a (Argentina) and any other name your authority uses. Fire does not validate it against a list and never branches on it.
Do not confuse these with the Document types catalog: that endpoint lists the buyer identification documents (
CEDULA, CPF, NIT…), not fiscal document types.string
The document’s visible number, as the authority composes it (
12388, 001-020-000000123, C012001). The pieces it is built from still travel in document with the country’s names. Required on authorized: Fire no longer deduces it.string
UTC date/time (ISO 8601) the document was issued. Same name as in the fiscal numbering response. Required on
authorized.string
UTC date/time (ISO 8601) the authority approved the document — a different fact from
issuedAt. They can be seconds apart (online authorization) or hours apart (contingency authorized when the connection returns). Optional; when absent, event consumers see authority.authorizedAt: null. On a cancellation use cancelledAt instead: a cancelled callback keeps the issuedAt and authorizedAt the document already had — they are the invoice’s — and only uses the ones you send if Fire had none.string
UTC date/time the authority approved the cancellation. Send it on
cancelled.string
ONLINE, OFFLINE or BATCH. OFFLINE is contingency: the document is valid but was issued without reaching the authority, and some regimes require the receipt to say so (the Ecuadorian RIDE prints EMISION POR CONTINGENCIA).string
Download link for the PDF (DANFE / RIDE). HTTPS, downloadable by direct GET, valid for at least 24 h — Fire archives it. Required for
authorized and cancelled.string
Download link for the authority’s legal XML. Same conditions as
pdfUrl. Required for authorized and cancelled.number
Gross total of the document, in real decimals (not scaled). Required on
authorized.number
Total tax of the document, in real decimals. Required on
authorized.string
ISO 4217 code — exactly 3 letters (
BRL, COP, USD). Required on authorized.string
The document’s id in your system. It is what the PDF and XML are fetched by and what a cancellation is requested with. Required on
authorized.object
What gets printed: a map of key → exact string to encode (
{ "qr": "…" }, { "ted": "…" }). The key names the purpose; how it is drawn is the receipt template’s choice. It sits at the root, next to document, as in the fiscal numbering response. Required and non-empty on fiscal_graphic; optional otherwise.object
Who issued it and with what version. It reaches the order and the events as
authority.providerIdentity — the same name as in the fiscal numbering — with the three keys always present. If you do not send provider, or send it empty, providerIdentity is null.object
Why there is NO document. Required when
eventType is rejected, denied or error.Formerly named error, and it now carries the same shape as the fiscal numbering response.Show failure
Show failure
string
Optional provider/authority error code (e.g.
DIAN_42, cStat_204).string
TECHNICAL or FUNCTIONAL. This is what the channel branches on: with TECHNICAL the document may still be issued and the receipt prints “pending”; with FUNCTIONAL the authority rejected its content and it will not be issued.string
required
Human-readable message. At least 1 character.
array
Field-by-field detail, when the authority provides it: a list of
{ "field": "…", "issue": "…" }, both optional.object
Free-form bag for provider-specific extras (raw response, internal IDs, etc.). Not validated. It reaches the order and the events as
authority.providerMetadata, as is and opaque — just like in the fiscal numbering. Absent or {} becomes null. Do not send null: omit it or send {} — null returns 400.Formerly named providerSpecific.The root takes no extra keys. Only the fields above are read; any other root key is silently discarded — it is not rejected.
orderCode is not part of the contract — the order is resolved by orderId.What is required, per event
| Field | authorized | cancelled | fiscal_graphic | rejected · denied · error |
|---|---|---|---|---|
document | required | required | required | — |
documentType | SALE_INVOICE | CREDIT_NOTE | — | — |
docSubtype | required | required | — | — |
pdfUrl, xmlUrl | required | required | — | — |
documentNumber, providerDocId, issuedAt | required | — | — | — |
totalAmount, taxAmount, currencyCode | required | — | — | — |
graphic | optional | optional | required, non-empty | — |
failure | — | — | — | required |
authorizedAt, cancelledAt, authorizationMode and provider are always optional. A required field sent as null counts as missing.
Document by country
The required shape ofdocument depends on country. document carries ONLY that country authority identifiers — the fields common to all six live at the root of the payload, above.
document changed contentsUntil now document mixed two things: what means the same in every country —docType,
pdfUrl, xmlUrl, the amounts, the dates— and each authority own vocabulary. That mix
propagated to everything that read the callback.Now the canonical fields live at the root and document keeps only the regime’s. Same
shape as the fiscal numbering response: the same fact, in the same place, regardless of
which path it came in through.Other changes in the same batch: countryCode → country, docType → documentType
(SALE_INVOICE / CREDIT_NOTE, no alias), error → failure (with scope and
details), providerSpecific → metadata, and emittedAt → issuedAt. New at the root:
documentNumber, authorizedAt and graphic.- Brazil (BR)
- Colombia (CO)
- Ecuador (EC)
- Chile (CL)
- Argentina (AR)
- Venezuela (VE)
Brazilian fiscal documents (NF-e / NFC-e). Use this country code when sending callbacks from any BR fiscal provider.In addition to the canonical root fields (
The example below is a complete
documentType, docSubtype, documentNumber, pdfUrl, xmlUrl, issuedAt, etc.), BR requires these specific identifiers:| Field | Type | Required | Notes |
|---|---|---|---|
chaveAcesso | string | ✓ | Exactly 44 digits — SEFAZ access key |
protocolo | string | ✓ | SEFAZ authorization protocol |
numero | int / string | ✓ | Document number |
serie | int / string | null | Document series (encoded in chave) | |
modelo | 55 / 65 | null | 55 = NF-e, 65 = NFC-e | |
cStat | int / string | null | SEFAZ status code: 100 authorized, 135 cancellation registered, 865 rejected. Reaches the events as authority.countryData.cStat |
authorized payload: every root field it shows is required for that event, except provider and metadata, which are optional and reach the order as authority.providerIdentity and authority.providerMetadata — see What is required, per event.{
"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
}
}
Colombian fiscal documents (DIAN).
| Field | Type | Required | Notes |
|---|---|---|---|
cufe | string | ✓ | DIAN unique fiscal code |
prefijo | string | ✓ | Document prefix |
numeroDian | string | ✓ | DIAN document number |
qrCode | string (URL) | null | QR for the printed document | |
numeroComprobante | string | null | The visible number, already assembled: prefix + consecutive | |
ambiente | "1" | "2" | null | 1 production · 2 testing — the opposite of the SRI |
{
"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"
}
}
The names are the same as in the numbering, deliberately:
cufe, prefijo,
numeroDian, numeroComprobante, qrCode and ambiente mean exactly the same here as
in the prekey response. It is the same document told twice, and if the names diverged,
reconciling both paths would stop being a matter of comparing fields.numeroDian goes without the prefix (990000001, not SETP990000001). The assembled
number is numeroComprobante. Sending the assembled one in both makes reconciliation
compare SETP990000001 against 990000001, and they never match.Both fields are optional inside document: whoever already sends callbacks without them
keeps working. That does not extend to the root: the fields listed in
What is required, per event are required, and a callback missing
them receives 400.Ecuadorian fiscal documents (SRI).
| Field | Type | Required | Notes |
|---|---|---|---|
claveAcceso | string | ✓ | Exactly 49 digits — SRI access key |
numeroComprobante | string | null | The visible number, already assembled: estab-ptoEmi-secuencial | |
ambiente | '1' / '2' | null | 1 = test, 2 = production | |
establecimiento | string | null | Establishment code (001) | |
puntoEmision | string | null | Emission point code (020) | |
secuencial | string | null | Document sequence number (000000123) |
numeroAutorizacion was removed from the contract. Under the online authorization
scheme the SRI returns the access key itself as the authorization number, so it was
the same fact declared twice. If your system keeps sending it nothing breaks
—undeclared fields are preserved— but we neither require nor interpret it. Read
claveAcceso when you need the authorization number.The three parts of the number —establecimiento, puntoEmision, secuencial— now
exist on both paths: pre-emission numbering and this callback. They are optional: if
your provider does not emit them, do not send them. Fire does not derive them from
claveAcceso —that rule belongs to the regime— and the receipt prints without them.{
"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"
}
}
Chilean fiscal documents (SII DTE).
| Field | Type | Required | Notes |
|---|---|---|---|
folio | int / string | ✓ | DTE folio number |
ted | string | ✓ | TED (Timbre Electrónico) — base64 or XML fragment |
tipoDte | int / string | ✓ | DTE type (e.g. 33 = factura electrónica) |
trackId | string | null | SII tracking ID for follow-up queries |
{
"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"
}
}
Argentine fiscal documents (AFIP).
| Field | Type | Required | Notes |
|---|---|---|---|
cae | string | ✓ | Código de Autorización Electrónico |
fechaVtoCae | string | ✓ | CAE expiration date |
puntoVenta | int / string | ✓ | Point of sale number |
numeroComprobante | int / string | ✓ | Document number |
tipoComprobante | int / string | ✓ | Document type (1 = factura A, …) |
{
"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
}
}
Venezuelan fiscal documents (SENIAT).
| Field | Type | Required | Notes |
|---|---|---|---|
numeroControl | string | ✓ | Number from the control range issued by SENIAT |
numeroFactura | string | ✓ | Invoice number |
rifEmisor | string | null | Emitter RIF |
{
"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"
}
}
The fiscal_graphic state
fiscal_graphic reports that your provider is delivering the representación gráfica — what the till prints for the document. It is a state of its own, not an outcome:
- It is not an approval. Receiving
fiscal_graphicsays nothing about whether the authority approved the document. Fire stores the graphic and the document stays non-terminal. - It is optional. If your provider has no graphic for a document, send
authorized(or any terminal state) directly — nofiscal_graphicis required first. Both flows are valid. - It can share the
eventIdwith its outcome. The graphic and the terminal result belong to the same action, so you may sendfiscal_graphicand thenauthorized/rejected/denied/cancelledreusing the sameeventId. That is not a conflict and never returns409— see Idempotency & scenarios. - It requires
documentand a non-emptygraphic. The graphic representation is what the till prints — the QR, the TED, the encoded access key — and it travels in the rootgraphic. The PDF and the XML are the files of the authorized document and arrive withauthorized. - It triggers no outbound flow event.
order.invoicedandorder.reversedremain tied toauthorizedandcancelledrespectively. The graphic only changes the document’s state.
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"
}
}
Negative outcomes (rejected, denied, error)
For non-authorized states, omit document (or send it as null) and provide failure. country still applies (it validates routing); the document itself is not required because there is no authorized artifact — and neither are documentType/docSubtype, since there is no document to type.
A negative outcome stores no receipt data. If you send documentNumber, providerDocId, issuedAt, authorizedAt, amounts, pdfUrl, xmlUrl or graphic with rejected, denied or error, Fire ignores them: only documentType, docSubtype, the country’s document keys, failure, provider and metadata are kept. A document that already had receipt data keeps it.
Rejected
{
"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" }
}
Retries and rejected cancellations
A failed issuance can be retried. If you reportedrejected, denied or error and then retry with the authority, send the new result with the same eventId — the issuance’s. Fire accepts it and the flow continues normally: if the new result is authorized, order.invoiced is dispatched. If the retry fails again, the new reason replaces the previous one.
rejected (eventId E1) → authorized (eventId E1) ✓ applied, order.invoiced dispatched
rejected (eventId E1) → error (eventId E1) ✓ new reason stored
order.cancelled event), the order is left “cancelling”. If the authority does not accept the cancellation, send rejected, denied or error with the eventId of that cancellation and the reason in failure. Fire reads it as a failed cancellation: the invoice remains valid (the order goes back to authorized, with the reason in error) and no event is dispatched. If you later retry and the authority accepts it, send cancelled with the same eventId and order.reversed is dispatched.
order.cancelled (eventId C1) → rejected (C1) → the invoice stays authorized
→ cancelled (C1) → voided, order.reversed dispatched
A
rejected on an authorized invoice that is not being cancelled still means what it always did: the authority rejected the invoice.Response
On success the endpoint returns202 Accepted — the event was authenticated, validated, deduplicated, and enqueued. A 202 does not mean the fiscal document was updated yet; that happens asynchronously in a background worker. Use the status endpoint to confirm the final outcome.
The body carries two distinct ids so there is no ambiguity: eventId is the id you sent (echoed back), webhookEventId is Fire’s id for the queued record.
boolean
Always
true when the request was accepted and enqueued.boolean
true when this (orderId, eventId) was already ingested with the same eventType — the existing record is returned and nothing is re-enqueued. false for a fresh event. A different eventType on the same pair never reaches this field: it returns 409, except fiscal_graphic.string
Echo of the
eventId you sent (the Fire emission’s event.id). Use it to correlate this acknowledgement with your request.string
Fire’s id for the queued record in
webhook_events. Pass it to GET /v1/webhooks/events/{webhookEventId} to poll the processing outcome. On a duplicate it is the same id returned the first time.string
Current queue status of the record —
queued → processing → processed (and retry / failed / dead / ignored).string
ISO 8601 UTC timestamp of when Fire first received this event. Stable across retries — useful as a trace anchor.
string
Human-readable summary of what happened.
{
"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."
}
Checking the outcome
Because processing is asynchronous, the202 only confirms the event was queued. To see whether the fiscal document was actually updated, poll the companion status endpoint with the webhookEventId returned by the 202:
GET https://app.fire.rest/api/v1/webhooks/events/{webhookEventId}
x-api-key: <your webhooks:fiscal key>
string
Queue lifecycle:
queued → processing → processed (done) · failed / dead (gave up after retries) · retry (waiting for the next attempt) · ignored.number
Processing attempts so far.
object | null
On success, the worker outcome — e.g.
{ "kind": "updated", "documentId": "…", "receiptId": "…" }.object | null
{ "message": "…" } when the last attempt failed; null otherwise.The
eventType returned here is the one you sent for that queued record. If you sent fiscal_graphic and then the terminal state under the same eventId, each is its own queue record with its own webhookEventId — poll the one you care about.{
"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 is returned for unknown ids — or ids that belong to another tenant — with no existence leak. Authenticate with the same webhooks:fiscal key you used for the callback.
Idempotency & scenarios
Fire deduplicates callbacks on(orderId, eventId) — not on providerEventId (which you may regenerate freely). The same pair resent with the same eventType is a benign replay; the same pair with a different eventType returns 409 — an attempt to ride one action on another’s event — except fiscal_graphic, which may share the eventId with its outcome.
fiscal_graphic is the exception. It is a step of the same action, not a competing outcome, so it may share the eventId with the terminal state that follows it — that combination is accepted, never a 409.
This is the full behavior matrix — every combination you can send:
| Scenario | Result |
|---|---|
New (orderId, eventId) | 202 · duplicate: false — enqueued |
Same (orderId, eventId) with the same eventType resent (any providerEventId) | 202 · duplicate: true — existing record returned, not re-enqueued |
fiscal_graphic then a terminal state on the same eventId | 202 both times — the graphic is a step of the same action, not a conflict |
A failure (rejected, denied or error) then another outcome with the same eventId (e.g. authorized after a retry) | 202 — it is a retry of the same issuance and is applied |
The same failure resent with the same eventId (e.g. a second rejected with another reason) | 202 · duplicate: false — processed, the new reason is stored. A failure dispatches no events, so reprocessing it is harmless |
Same (orderId, eventId) but different terminal eventType (e.g. cancelled echoing the authorized’s eventId) | 409 — rejected. Use the event that belongs to this action |
eventId not emitted by Fire for this orderId | 400 — wrong/invented eventId |
| Order/event not under your API key’s account + vendor | 403 |
| Malformed payload (Zod), missing required fields | 400 |
| Missing / invalid API key | 401 |
| Auth store momentarily unreachable | 503 — transient, retry |
A cancellation needs its own event. You cannot cancel a document by resending the authorization’s
eventId with eventType: "cancelled" — that returns 409. Fire is the source of truth: a cancellation must reference the cancellation/reversal event Fire emitted (a distinct eventId). Returning a 202 duplicate here would wrongly tell you the cancellation was accepted while Fire never cancelled — so Fire rejects it explicitly instead.409 vs 202. A 409 is the only case where a well-formed, authenticated callback is refused at the idempotency layer — because accepting it would diverge state. A same-type replay is never an error: it returns 202 so your retries stay clean. A 503 is ours (transient infra), so it is safe and expected to retry.Concurrency
Status transitions onfiscal_documents use optimistic locking. If two callbacks for the same document arrive concurrently, one succeeds and the other resolves to idempotent or regression depending on the order. The orders.fiscal JSONB merge is atomic.
When the order already has a document, a callback is matched to it by eventId. A callback with a different valid eventId ends in notFound, not regression; resending the document’s own eventId with an earlier state is a same-pair replay. Neither changes the document.
What happens after the 202
From the callback to the events
How each field you send here reaches
order.invoiced and order.reversed.Fiscal overview
Numbering, callback and events in one picture.
202 only enqueues the event. A background worker then picks it up — within ~2 seconds via the instant wake-up, or on the next poll cycle as a fallback — and:
- The fiscal document is updated with the new status and the authority’s identifiers.
- The verdict is stored on the order, structured: canonical fields,
amounts,countryDataandgraphic— and appended to the order’s fiscal history. - An outbound event is dispatched, for every country:
order.invoicedforeventType=authorized,order.reversedforeventType=cancelled. Active Integration Flows for that event run and POST to your endpoint.fiscal_graphic,rejected,deniedanderrorupdate the state but emit no event.
From the callback to the events
Whoever consumesorder.invoiced or order.reversed reads exactly what you sent here, reorganized into the event’s fiscal block. The full reference of that block is in order.invoiced.
The root fields land in fiscal.authority (or cancellation.fiscal.authority) with the same names, except the three amounts, which are grouped in authority.amounts. document becomes authority.countryData, and provider and metadata become providerIdentity and providerMetadata (null when absent or empty). The field-by-field table is in From the callback to the events.
document and countryData are the same block. Fire does not keep a list of each country’s keys: anything in document that is not a canonical field reaches countryData untouched. A new identifier your authority starts requiring travels to the events with no change on Fire’s side.
Why this endpoint keeps its /v1 path
The field renames above (docType → documentType, countryCode → country, error → failure, providerSpecific → metadata) break a sender that still uses the previous names: it receives 400 naming the field. The endpoint nonetheless kept its path. When the contract changed, this callback only carried test traffic in production, and Fire’s own test client moved in the same release, so a parallel /v2 would have kept two ways of saying the same thing for no sender. The event side is different — it has production consumers — and the three affected events each published a new documented version.
Related
order.completed
See where the fiscal state lands inside the V4 order snapshot.
order.invoiced
Outbound event triggered by an
authorized callback, for every country.Inject order
The order injection endpoint that creates the order this callback updates.
Authentication
How API keys, scopes, and vendor binding work.

