Webhooky

Webhooky vám doručia udalosti (faktúry, platby, sklad, Peppol doklady) na váš HTTPS endpoint. Každé doručenie je podpísané HMAC‑om a opakuje sa s exponenciálnym backoffom, kým ho nepotvrdíte HTTP 2xx. Firma s vlastným API kľúčom si endpoint registruje cez API (prvá sekcia), partner v portáli (ďalšie sekcie). Podpis, hlavičky a opakovania sú v oboch prípadoch rovnaké.

Webhooky pre firmy (vlastný API kľúč)

Endpoint zaregistrujete API kľúčom s rozsahom settings:update. Funguje od plánu Pro. Odpoveď obsahuje podpisový secret iba raz. Najviac 20 endpointov na firmu, URL musí byť https://. Rovnaké endpointy vidíte v eDoklade v Nastavenia → Webhooky. Okamžité spúšťače v Make a n8n si endpoint vytvárajú a mažú samy.

curl -X POST https://api.efaktura.sk/v1/agent/webhooks \
  -H "X-API-Key: efk_live_…" \
  -H "X-Organization-Id: <uuid>" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://erp.example.sk/efaktura","events":["peppol.document.received","invoice.paid"]}'
UdalosťKedy
invoice.createdVznikol doklad — v appke, cez API, importom, opakovanou faktúrou, z e-shopu.
invoice.updatedZmenil sa stav, číslo, suma alebo splatnosť.
invoice.deletedDoklad bol zmazaný.
invoice.sentStav faktúry je odoslaná; invoice.viewed = odberateľ otvoril odkaz.
invoice.paidFaktúra je uhradená (banka aj ručne).
invoice.overdueFaktúra je po splatnosti.
payment.receivedK faktúre sa spárovala platba.
reminder.sentOdišla upomienka.
peppol.document.*sent / delivered / failed / received — ako pri partneroch nižšie.
stock.*level_changed / document_confirmed / low — sklad.

Udalosti faktúr nesú v data polia invoiceId, invoiceNumber, type, status, previousStatus, total (string "149.99"), currency, issueDate, dueDate, paidDate, customerName a pdfUrl. Históriu doručení vráti GET /v1/agent/webhooks/{id}/deliveries, opätovné odoslanie POST …/deliveries/{deliveryId}/redeliver, testovaciu udalosť POST /v1/agent/webhooks/{id}/test. Doručenia uchovávame 90 dní. Kompletný zoznam endpointov je v API referencii (sekcia Webhooks).

Partneri: registrácia endpointu

Endpoint zaregistrujete v portáli v sekcii Webhooky alebo cez POST /v1/partner/webhooks — to je endpoint portálu overený vaším prihlásením (session cookie), nie hlavičkou X-API-Key; s API kľúčom vráti 401 UNAUTHORIZED. Pri vytvorení dostanete podpisový secret iba raz. Uložte si ho. Jeden partner môže mať najviac 20 endpointov; URL musí byť https://. Endpoint štandardne dostáva udalosti za všetky organizácie partnera; voliteľným orgId pri vytvorení ho obmedzíte na jedinú organizáciu.

Partneri: typy udalostí

EventKedy
peppol.document.sentDoklad prijatý sieťou (AS4 receipt).
peppol.document.deliveredDoklad doručený príjemcovi (MLS/doručenka).
peppol.document.failedOdoslanie zlyhalo (finálne).
peppol.document.receivedPrišiel vám príchodzí doklad cez Peppol.
peppol.document.fs_report_acceptedFinančná správa prijala hlásenie k odoslanému alebo prijatému dokladu.
peppol.document.fs_report_rejectedFinančná správa hlásenie zamietla; dôvody sú v data.issues.
participant.activatedOrganizácia je zaregistrovaná v SMP a prijíma doklady (po potvrdení FS mandátu alebo po POST /v1/agent/peppol/enroll).
participant.failedRegistrácia účastníka zlyhala (neplatný FS token, chyba SMP, kolízia s inou firmou, deaktivovaná organizácia).
participant.deactivatedOrganizácia bola odregistrovaná zo SMP (POST /v1/agent/organizations/{id}/peppol/deactivate).
usage.limit_exceededOrganizácia prekročila zahrnutý mesačný objem transakcií.

Payload udalostí participant.* nesie data.participantId (0245:DIČ, v sandboxe 9915:DIČ), data.scheme, data.mode (live / test), data.source (partner_claim / api_enroll / api_deactivate) a časovú pečiatku activatedAt / failedAt / deactivatedAt; participant.failed navyše data.code a data.message.

Payload udalostí peppol.document.fs_report_* nesie data.direction (sent / received), data.invoiceId alebo data.receivedDocumentId, data.originalDocumentId, data.state, data.issues a data.decidedAt. Aktuálny stav hlásenia je aj v poli fs_report v GET /v1/agent/peppol/status/{invoiceId} a GET /v1/agent/peppol/received/{id}.

Event peppol.document.status_changed je v katalógu, ale zatiaľ sa neemituje. Zmeny stavu prenosu pokrývajú eventy sent / delivered / failed vyššie.

Formát payloadu

Telo je JSON s obálkou event / timestamp (ISO 8601) / data. Pole data.orgId je vždy prítomné a identifikuje klientsku organizáciu. Pri test sendoch nesie data.mode = "test".

{
  "event": "peppol.document.delivered",
  "timestamp": "2026-06-12T19:48:01.000Z",
  "data": {
    "invoiceId": "b1f0…",
    "invoiceNumber": "2026001",
    "documentType": "invoice",
    "mode": "test",
    "state": "DELIVERED",
    "messageId": "…",
    "transactionId": "…",
    "orgId": "0a2c…"
  }
}
EventPolia v data (okrem orgId)
sentinvoiceId, invoiceNumber, documentType, mode, state, messageId, transactionId
deliveredinvoiceId, invoiceNumber, documentType, mode, state, messageId, transactionId
failedinvoiceId, invoiceNumber, documentType, error
receivedsenderName, senderParticipantId, documentNumber, documentType, total, currency

Hlavičky

HlavičkaPopis
X-Webhook-SignaturePodpis vo formáte t=<unix>,v1=<hex> (Stripe model).
X-Webhook-EventNázov udalosti.
X-Webhook-IdUUID doručenia (idempotenčný kľúč).
X-Webhook-TimestampUnix čas podpisu (sekundy).

Overenie podpisu

Podpisuje sa reťazec <timestamp>.<raw body> pomocou HMAC‑SHA256 a vášho secretu, výsledok je lowercase hex v segmente v1=. Počas rotácie secretu môže hlavička obsahovať viac segmentov v1= (starý + nový): stačí, ak sa zhoduje ktorýkoľvek. Porovnávajte v časovo‑konštantnom čase a odmietnite príliš staré timestamp (ochrana proti replayu).

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("="))
  );
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;

  const expected = createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  // header may carry several v1=… segments during secret rotation
  return header
    .split(",")
    .filter((kv) => kv.startsWith("v1="))
    .some((kv) => {
      const got = kv.slice(3);
      return (
        got.length === expected.length &&
        timingSafeEqual(Buffer.from(got), Buffer.from(expected))
      );
    });
}
Použite raw telo requestu (bajt po bajte), nie znova serializovaný JSON. Inak podpis nebude sedieť.

Doručovanie a opakovania

Doručenie je durabilné (fronta + perzistovaná história). Endpoint má odpovedať HTTP 2xx do niekoľkých sekúnd; spracovanie spravte asynchrónne. Pri zlyhaní (ne‑2xx alebo timeout) sa doručenie opakuje 10× počas približne 2,7 dňa: spolu 11 pokusov, takže endpoint, ktorý cez víkend nebeží, dostane všetky udalosti.

PokusČakanie po predošlom zlyhaní
2.5 s
3.1 min
4.5 min
5.30 min
6.2 h
7.5 h
8.10 h
9.10 h
10.14 h
11. (posledný)24 h

Po poslednom neúspešnom pokuse sa doručenie označí ako zlyhané; z portálu ho stále viete znovu odoslať (replay).

MechanizmusSprávanie
OpakovaniaIniciálny pokus + 10 opakovaní počas ~2,7 dňa (tabuľka vyššie).
IdempotenciaDeduplikujte podľa X-Webhook-Id: replay pošle rovnaké ID.
Auto‑vypnutiePo 15 po sebe zlyhaných doručeniach sa endpoint deaktivuje.
Rotácia secretuStarý secret overuje ďalších 24 h (dvojitý v1= podpis).

Schránka udalostí (pull)

Každá udalosť sa zapíše aj do schránky udalostí, a to aj keď nemáte zaregistrovaný žiadny endpoint. Ak webhook prijať nemôžete (firewall) alebo výpadok trval dlhšie ako opakovania, stiahnite si zmeškané udalosti cez GET /v1/agent/events partnerským kľúčom. Telo v poli payload je presne to, čo by poslal webhook.

curl "https://api.efaktura.sk/v1/agent/events?after=1841&limit=100" \
  -H "X-API-Key: efk_pk_live_…"
PravidloSprávanie
KurzorPošlite after=<next_after> z predošlej odpovede, kým has_more je true.
PoradieOd najstaršej udalosti; id je rastúce číslo (string).
DeduplikáciaPodľa event_id, ak je vyplnené; inak sa udalosť zapíše pri každom výskyte.
Uchovanie90 dní.
RozsahVšetky vaše organizácie; každá udalosť nesie org_id.

Testovanie

V portáli viete poslať testovací event na endpoint, pozrieť si históriu doručení (payload + odpoveď) a znovu‑odoslať (replay) ktorékoľvek minulé doručenie. V sandboxe (test kľúč) fire‑uje reálny peppol.document.delivered s data.mode = "test" po doručení medzi zaregistrovanými test účastníkmi.