Async issuance
How issuance processing, tracking and idempotent retries work.
POST /v1/invoices responds immediately: a 202 with the invoice in an early status (queued, then processing/pending) means the request was registered and queued — not that the fiscal authority authorized it. Actual processing is asynchronous.
Tracking
- Poll
GET /v1/invoices/:idand followstatus/stageuntilauthorized(or a terminal failure). - Fetch the transition history with
GET /v1/invoices/:id/events. - Subscribe to webhooks (
nfse.invoice.authorized,nfse.invoice.documents_ready, etc.) to avoid polling. - Check
GET /v1/billing/usage(canEmit) before issuing again.
Idempotency-Key
The Idempotency-Key header is optional on issuance and email requests, but recommended — it makes retries safe. Use a stable key per business operation (e.g. the order id in your system) and keep the same key and the same payload on every retry.
The same key, account and environment with content equivalent to the computed hash recovers the same invoice; different content returns 409 (ERROR_IDEMPOTENCY_CONFLICT). The hash is computed before fiscal normalization, with canonical JSON keys and the competence date — do not change how fields are represented between retries (a CNPJ with and without punctuation can produce different hashes).
javascript
const key = `pedido_${pedidoId}_emissao`; // stable per business operation
async function issueWithRetry(buildBody, attempts = 3) {
for (let i = 0; i < attempts; i++) {
const res = await fetch(`${BASE_URL}/invoices`, {
method: "POST",
headers: {
"X-API-Key": process.env.CORVO_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(buildBody()),
});
if (res.status === 409) {
// ERROR_IDEMPOTENCY_CONFLICT — payload differs; fix the data, don't retry
throw new Error("Idempotency conflict: payload changed between retries");
}
if (res.status < 500) return res.json(); // success or client error — no retry
await new Promise((r) => setTimeout(r, 2000 * 2 ** i)); // backoff on 5xx
}
throw new Error("Issuance request failed after retries");
}