Webhook-ovi fiskalizacije: četiri događaja, HMAC potpis i ponovna isporuka

10 min čitanja

Kad račun ne nastane u istom pozivu, sistem koji ga je tražio mora da sazna ishod bez stalnog ispitivanja. Tekst opisuje webhook-ove Tezge: koji događaji postoje, kako se proverava potpis, kad se isporuka ponavlja i kako primalac da se zaštiti od duplih poruka.

Tezga šalje POST na tvoju adresu kad račun bude fiskalizovan, kad fiskalizacija ne uspe, kad se izda refundacija i kad se račun stornira. Poruka nosi HMAC-SHA256 potpis tela, a neuspela isporuka se ponavlja posle 1, 5, 15, 60 i 360 minuta. Primalac mora da uradi tri stvari: da proveri potpis na sirovom telu, da brzo odgovori sa 2xx i da isporuku obradi najviše jednom, po identifikatoru isporuke.

Ukratko
  • Četiri događaja: racun.fiskalizovan, racun.greska, refundacija.izvrsena i storno.izvrsen.
  • Zaglavlja: X-Tezga-Event, X-Tezga-Delivery i X-Tezga-Signature (sha256= i HMAC-SHA256 sirovog tela).
  • Uspeh je bilo koji odgovor 2xx u roku od 10 sekundi; sve ostalo se ponavlja, ukupno šest pokušaja u oko 7,4 sata.
  • Do 5 adresa po firmi, svaka sa svojom tajnom; tajna se vidi jednom, kad se napravi.
  • Webhook je deo API veze, pa važi za paket Biznis i Enterprise nivo.
  • Isporuka nije zamena za proveru stanja: za usklađivanje koristi GET /documents po svom externalId.

Koji događaji stižu i kada?

Događaj nastaje kad se promeni ishod dokumenta koji je tvoj sistem tražio. Isporuka se ne šalje u toku poziva: tvoj zahtev dobija odgovor odmah, a webhook ide posle odgovora, da tvoj sistem ne čeka na tuđi server.

DogađajKad nastajeŠta tvoj sistem radi
racun.fiskalizovanRačun izdat i fiskalizovan, bilo u prvom pozivu ili posle automatskog ponavljanjaUpiše PFR broj i link na račun u porudžbinu, pošalje potvrdu kupcu
racun.greskaRačun izdat, fiskalizacija nije uspela (odgovor je bio 202)Označi porudžbinu kao „račun u toku”, ne alarmira kupca
refundacija.izvrsenaRefundacija izdata, puna ili po stavkamaZatvori povraćaj u ERP-u i u knjizi plaćanja
storno.izvrsenRačun storniranPoništi zaduženje kupca

Ponovljen zahtev sa istim externalId ne šalje drugi događaj: webhook se beleži samo za nov dokument. Čist B2B tok sef se ne fiskalizuje, pa za njega nema fiskalnog događaja; ishod slanja na SEF čita se iz odgovora, kako je opisano u tekstu o B2B kupcu u web shopu.

Tok isporuke webhook-a i ponavljanje Tezga događaj + potpis POST, 10 s Tvoj server provera potpisa, dedup 2xx Isporučeno status: uspeh ostalo ili tajmaut Ponavljanje posle 1, 5, 15, 60, 360 min pa odustalo, vidi se u podešavanjima

Prvi pokušaj ide odmah posle odgovora na tvoj zahtev, a ponavljanja po rastućem razmaku: kratak ispad se preskoči za minut, duži ne pravi hiljade poziva.

Kako izgleda poruka i kako se proverava potpis?

Telo je JSON sa nazivom događaja, vremenom, identifikatorom firme i opisom dokumenta, u kome je i tvoj externalId, pa uparivanje ne traži Tezgin identifikator. Potpis je HMAC-SHA256 nad sirovim telom, sa tajnom koja počinje sa whsec_. Proveravaj ga pre bilo kakvog parsiranja, jer ponovno serijalizovan JSON ne daje iste bajtove.

POST /vas-webhook
X-Tezga-Event: racun.fiskalizovan
X-Tezga-Delivery: 5c0e...
X-Tezga-Signature: sha256=...

{
  "dogadjaj": "racun.fiskalizovan",
  "vreme": "2026-10-15T10:00:03.000Z",
  "tenantId": "...",
  "podaci": {
    "documentId": "b3f1...",
    "broj": "RAC2026000123",
    "fiskalni": { "pfrBroj": "AB12CD34-AB12CD34-123" },
    "externalId": "WEB-100245981",
    "provider": "custom"
  }
}
// Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

export function potpisIspravan(telo, zaglavlje, tajna) {
  const ocekivano = "sha256=" + createHmac("sha256", tajna).update(telo).digest("hex");
  const a = Buffer.from(zaglavlje ?? "");
  const b = Buffer.from(ocekivano);
  return a.length === b.length && timingSafeEqual(a, b);
}
<?php
// PHP: $telo je file_get_contents('php://input'), pre json_decode
$ocekivano = 'sha256=' . hash_hmac('sha256', $telo, $tajna);
$ispravan = hash_equals($ocekivano, $_SERVER['HTTP_X_TEZGA_SIGNATURE'] ?? '');

Šta je tačno ponašanje isporuke?

  • Pokušaji. Prvi pokušaj i do pet ponavljanja, posle 1, 5, 15, 60 i 360 minuta, ukupno u rasponu od oko 7,4 sata. Ponavljanja pokreće pozadinski posao koji radi na svakih 5 minuta, pa se kratak razmak računa kao „najranije”.
  • Tajmaut. Primalac ima 10 sekundi. Odgovori sporije, i isporuka se računa kao neuspela.
  • Identifikator isporuke. X-Tezga-Delivery je isti za sva ponavljanja jedne poruke, pa je to ključ za deduplikaciju.
  • Odustalo. Posle poslednjeg neuspeha red ostaje označen kao „odustalo” sa porukom, vidljiv u Podešavanja, Integracije. Ne nestaje tiho.
  • Adresa. Mora biti javni HTTPS. Zaštita ne dozvoljava da bi firma naterala Tezgin server da kuca po internim adresama.
  • Paket. Firma koja izgubi pristup API-ju više ne dobija nove događaje, a poruke koje su čekale se zatvaraju kao „odustalo” sa porukom o paketu.

Kako izgleda dobar primalac?

Pet pravila, po ovom redosledu. Prvo, proveri potpis nad sirovim telom i odbij sa 401 ako ne valja. Drugo, zapiši X-Tezga-Delivery pre bilo kakve obrade; ako je već viđen, odgovori sa 200 i ne radi ništa. Treće, odgovori sa 2xx čim je poruka bezbedno zapisana (u red ili tabelu), a obradu radi posle. Četvrto, upari po externalId, ne po Tezginom identifikatoru. Peto, ne pretpostavljaj redosled: za isti račun može prvo da stigne racun.greska, a kasnije racun.fiskalizovan, pa čuvaj stanje po vremenu događaja (polje vreme), ne po tome koja je poruka poslednja stigla.

Webhook ne zamenjuje proveru stanja. Ako tvoj server bude van mreže duže od sedam sati, poruke se odustaju. Zato jedan posao u tvom sistemu jednom dnevno poredi otvorene porudžbine sa GET /documents po externalId. Ta dva mehanizma zajedno daju sistem koji ne gubi račun ni u slučaju ispada sa tvoje, ni sa naše strane. Šta Tezga radi kad Poreska ne odgovara opisano je u tekstu kad Poreska uprava ne radi.

Izvori: OpenAPI opis Tezga eKase, verzija 1.6.0 (pos.narbiz.com/api/openapi.json), ИБ 1597; uputstvo za integratore (pos.narbiz.com/api-dokumentacija.pdf); strana Enterprise sa opisom događaja (tezgafiskal.com/enterprise); RFC 2104 (HMAC) i RFC 6234 (SHA-2).

Česta pitanja

Koliko puta Tezga pokušava da isporuči webhook?

Prvi pokušaj i do pet ponavljanja, posle 1, 5, 15, 60 i 360 minuta, što je ukupno šest pokušaja u rasponu od oko 7,4 sata. Posle poslednjeg neuspeha poruka ostaje označena kao „odustalo” sa razlogom, vidljiva u podešavanjima integracije.

Kako da znam da je poruka zaista od Tezge?

Proveri zaglavlje X-Tezga-Signature: to je sha256= i HMAC-SHA256 sirovog tela, potpisan tajnom adrese koja počinje sa whsec_. Poredi u konstantnom vremenu i nad sirovim telom, pre parsiranja JSON-a.

Šta ako moj server primi istu poruku dvaput?

Ponavljanja nose isti X-Tezga-Delivery, pa zapiši taj identifikator i ponovljenu poruku preskoči uz odgovor 200. Takva dedupe tabela je jedini pouzdan način, jer odgovor može da se izgubi na putu i kad je primalac uspešno obradio poruku.

Koliko webhook adresa mogu da imam?

Do 5 adresa po firmi, svaka sa svojom tajnom, pa ERP, CRM i sistem za mejlove mogu da primaju poruke nezavisno. Tajna se prikazuje jednom, kad se adresa napravi, a čuva se šifrovano.

Zašto ne stiže racun.fiskalizovan za moju B2B porudžbinu?

Verovatno je tok sef, koji ne fiskalizuje dokument, pa fiskalnog događaja nema. Ishod slanja e-fakture je u polju sef odgovora na /order, a stanje dokumenta se uvek može proveriti kroz GET /documents.

Mogu li adresa za webhook da bude http ili interna?

Ne. Adresa mora biti javni HTTPS. Zaštita sprečava da se Tezgin server navede da kuca po internim adresama, pa adrese sa privatnim ili lokalnim IP opsezima ne prolaze.

Da li webhook radi na svim paketima?

Odlazni webhook je deo API veze, a ona je u paketu Biznis i na Enterprise nivou. Firma koja izgubi pristup API-ju ne dobija nove događaje, a poruke koje su čekale u redu se zatvaraju sa porukom o paketu.

Šta da radim ako moj server bude van mreže duže od sedam sati?

Poruke se u tom slučaju odustaju, pa pokreni usklađivanje: za otvorene porudžbine pozovi GET /documents po externalId i uzmi stanje direktno. Zato preporučujemo dnevni posao usklađivanja uz webhook-ove, ne umesto njih.

Tok možeš da isprobaš bez pravih računa: u simulaciji PFR broj počinje sa MOCK i ništa ne ide Poreskoj, pa provera potpisa i deduplikacije može da se uradi pre produkcije. Probni pristup traje 48 sati i otvara se na strani probaj fiskalnu kasu, a ceo API je opisan na strani API za fiskalizaciju.