Back to Journal
financeen

NAV Online Invoice v3.0 integration inside Netorigo Finance

14,200 invoices a month, 99.94% first-attempt success, three error categories and an exponential backoff up to 24 hours.

Since 2021 every B2B invoice issued in Hungary must be uploaded to the NAV Online Szamla v3.0 endpoint within five minutes of issuance. This is not a nice-to-have, it is the legal baseline. Inside the Netorigo Finance module this pipeline has been in production for two years; in May 2026 we handled 14,200 invoices with a 99.94% first-attempt success rate.

Context for non-Hungarian readers

NAV (Nemzeti Ado- es Vamhivatal) is the Hungarian tax authority. Their realtime invoice reporting system is mandatory for every taxable entity issuing B2B invoices in Hungary, regardless of where the company is headquartered. A German parent invoicing a Hungarian subsidiary, a French SaaS billing a Hungarian customer above the registration threshold, all of them must comply. The Finance module ships this compliance out of the box.

Three endpoints we touch

The v3.0 specification defines three key endpoints. First, tokenExchange: every five minutes we mint a fresh exchange token using certificate-based authentication, then attach it to subsequent manageInvoice calls. Second, manageInvoice itself: we submit the invoice XML, NAV stamps it with a transaction identifier and queues it for processing. Third, queryInvoiceData: we poll NAV-side status until the invoice reaches the terminal DONE or ABORTED state.

The core challenge is that NAV processes the XML asynchronously. A perfectly valid invoice may come back as PROCESSING after four seconds and DONE after thirty, or it may sit for six minutes while NAV experiences load. We use an outbox pattern in the NestJS controller, so no upload is ever lost, not even when NAV returns a mid-flight 502.

The six invoice types

NAV distinguishes six invoice types: INVOICE (standard), MODIFY (amendment, replacing the old storno-plus-reissue dance), BATCH_INVOICE (collective), STORNO (explicit cancellation), RECEIPT (cash register receipt), and OTHER (special). The Finance module handles all six. The MODIFY flow is the most common trap because it requires the NAV-side transactionId of the original invoice. We keep a nav_invoice_link table that joins our internal invoice PK to the NAV transactionId, indexed both ways.

Three error categories

The NAV API surfaces errors in three categories:

  1. Validation errors (SCHEMA_VALIDATION_ERROR, BUSINESS_VALIDATION_ERROR) — the XML is wrong. We move the upload to failed immediately and surface it for bookkeeper review. Typical examples: missing SZJ commodity code on a reverse-charge line, or a non-existent buyer tax number.

  2. Network errors (502, 503, 504, TIMEOUT) — NAV is unreachable. Our exponential backoff kicks in: 30s, 1m, 2m, 4m, 8m, 16m... up to 24 hours. After 24 hours we human-flag.

  3. Retry-able errors (INVOICE_NUMBER_NOT_UNIQUE, LOCKED) — transient state. We retry once after five seconds; if still bad, we promote to category 1.

The retry policy

A BullMQ worker treats every upload as a job and routes by error category. Category 1 (validation) never auto-retries — the bookkeeper fixes the source data. Category 2 (network) backs off exponentially for up to 24 hours. Category 3 (retry-able) gets one five-second retry and is then promoted to category 1.

Out of the 14,200 invoices in May, exactly nine reached human-flag status, all category 1, all real data issues (a mistyped tax number, a missing SZJ code). The remaining 14,191 reached DONE on the first attempt or inside the auto-retry envelope.

What we do not do

The module never overwrites a transactionId returned by NAV. Ever. Once NAV registers an invoice as DONE, that record is canonical from NAV's perspective, and the Finance module can only mutate it through MODIFY or STORNO operations. This audit-discipline rule eliminates roughly 90% of compliance disputes cheaply.

NAV integration is not a fizz-and-bang feature. You build it slowly, defensively, with an audit-first mindset. Then your partner books zero penalties across 18 months.