ePostman docs
Portál

Webhooky

Registrácia webhookov, tvar správy, overenie podpisu a katalóg udalostí.

4 min čítania
  • #webhook
  • #hmac-sha256
  • #x-webhook-signature
  • #udalosti
  • #retry

Namiesto opakovaného dotazovania nechajte platformu, nech vám zavolá sama.

Registrácia

curl -X POST https://testpostman.slovakodata.com/api/v1/webhooks \
  -H "X-Api-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://erp.mojafirma.sk/hooks/epostar",
    "events": ["invoice.delivered", "invoice.rejected", "invoice.accepted"]
  }'

# 201 Created — secret vygeneruje platforma, v odpovedi príde len raz
{
  "id": "b7e2f9a1-3c4d-4e11-9a2b-7f6c1d8e0a35",
  "url": "https://erp.mojafirma.sk/hooks/epostar",
  "events": ["invoice.delivered", "invoice.rejected", "invoice.accepted"],
  "secret": "whsec_...",
  "is_active": true,
  "created_at": "2026-07-13T09:10:00.000Z"
}

Ako events uveďte zoznam konkrétnych udalostí z katalógu nižšie. Sekret na podpisovanie doručovaných správ vygeneruje platforma a vráti ho len v tejto odpovedi na vytvorenie — pri neskoršom zozname webhookov už nikde nie je. Uložte si ho hneď; ak ho stratíte, treba webhook zmazať a založiť nový.

URL musí začínať https:// a smerovať na verejne dostupný server. Platforma odmietne s chybou 400 adresu bez https, s menom a heslom v URL, jednoslovný názov hosta (napr. https://erp/hook), interné domény (localhost, .local, .internal) aj IP adresy z privátnych a rezervovaných rozsahov. To isté overuje pri každom doručení: ak sa meno hosta preloží na takúto adresu, správa sa nedoručí a neopakuje. Presmerovania (3xx) platforma nenasleduje.

Tvar správy

{
  "event": "invoice.delivered",
  "timestamp": "2026-07-13T09:15:03.871Z",
  "data": {
    "invoice_id": "9f2c1e84-3b7a-4d16-b0c5-1e8a72d40f31",
    "mls_c3_status": null,
    "mls_c5_status": null,
    "status": "DELIVERED",
    "selfBilling": false,
    "invoiceTypeCode": "380",
    "document_type": "INVOICE",
    "invoice_number": "2026001",
    "supplier_name": "Dodávateľ s.r.o.",
    "buyer_name": "Odberateľ a.s.",
    "total_amount": "120.00",
    "currency": "EUR",
    "issue_date": "2026-08-23",
    "document_url": "https://postman.slovakodata.com/api/v1/invoices/9f2c1e84-3b7a-4d16-b0c5-1e8a72d40f31/xml"
  }
}

# Hlavičky
X-Webhook-Signature: 4f1c0a...      # HMAC-SHA256, hex
X-Webhook-Timestamp: 2026-07-13T09:15:03.871Z

Overenie podpisu

Podpis počítame z reťazca {timestamp}.{telo} pomocou HMAC-SHA256 a vášho secretu. Vždy ho overte — inak vám ktokoľvek môže podstrčiť falošnú udalosť. Na porovnanie použite funkciu odolnú voči časovej analýze.

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

export function isValidWebhook(
  rawBody: string,      // telo PRESNE tak, ako prišlo — nie re-serializované
  signature: string,    // X-Webhook-Signature
  timestamp: string,    // X-Webhook-Timestamp
  secret: string,
): boolean {
  // Staršie ako 5 minút zahodíme — ochrana proti prehratiu.
  // Hlavička je ISO 8601, nie epoch: Number() by nad ňou dal NaN a porovnanie
  // by vždy prešlo, takže by ochrana ticho nefungovala.
  const sentAt = Date.parse(timestamp);
  if (Number.isNaN(sentAt) || Math.abs(Date.now() - sentAt) > 300_000) return false;

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

  const a = Buffer.from(expected);
  const b = Buffer.from(signature);

  return a.length === b.length && timingSafeEqual(a, b);
}

Doručovanie a opakovanie

Čakáme na odpoveď najviac 30 sekúnd. Odpovedzte 2xx hneď, ako správu prijmete, a spracujte ju až potom na pozadí. Ak endpoint zlyhá alebo neodpovie, doručenie zaradíme na opakovanie.

Opakované pokusy sú podpísané rovnako ako prvý — rovnaký recept, rovnaký tvar hlavičiek. Líšia sa len v timestamp: každý pokus nesie čerstvý čas, ktorý je zároveň v podpísanom tele, takže päťminútové okno platí od daného pokusu, nie od pôvodnej udalosti. Overovanie napísané podľa prvého doručenia teda platí pre všetky pokusy.

Katalóg udalostí

UdalosťKedy príde
invoice.deliveredPrístupový bod odberateľa prijal odchádzajúcu faktúru a potvrdil to kladným AS4 potvrdením o prevzatí (receipt): status: "DELIVERED", mls_c3_status: null. Faktúra je odoslaná, doručenie ešte nie je potvrdené a kredit je stále rezervovaný. Ak MLS správa dorazí skôr, než stihneme túto udalosť odoslať, spravidla sa neodošle a príde rovno invoice.accepted alebo invoice.rejected. Pri príjemcovi mimo siete Peppol: finančná správa prijala daňové hlásenie, status: "DELIVERED_NON_PEPPOL" — tu je to konečný stav a kredit sa odpočíta.
invoice.acceptedPrístupový bod odberateľa poslal kladnú MLS správu — faktúra je doručená: status: "ACCEPTED", mls_c3_status: "OK". Vtedy sa odpočíta rezervovaný kredit. Neznamená to, že odberateľ faktúru obchodne schválil.
invoice.unconfirmedMLS správa nedorazila do 25 minút od odoslania; faktúra prešla do UNCONFIRMED a kredit sa odpočítal. Ak MLS správa dorazí neskôr, príde ešte invoice.accepted alebo invoice.rejected. Payload nesie len invoice_id a reason.
invoice.rejectedPrístupový bod odberateľa faktúru odmietol zápornou MLS správou — alebo pri príjemcovi mimo Peppolu odmietla daňové hlásenie finančná správa.
mls.receivedPrišla odpoveď k daňovému hláseniu, kým sa na potvrdenie od protistrany ešte čaká. Stav faktúry sa nemení.

Poradie udalostí nie je zaručené. invoice.delivered môže výnimočne prísť až po invoice.accepted alebo invoice.rejected k tej istej faktúre. O stave faktúry rozhodujte podľa poľa status v data, nie podľa názvu ani poradia udalostí: DELIVERED nikdy neprepisuje konečný stav, ktorý ste už prijali, a ten istý názov invoice.delivered nesie pri Peppole DELIVERED, mimo Peppolu DELIVERED_NON_PEPPOL.