ePostman docs
PortálConsole

WebhookyWebhooks

Registrácia webhookov, tvar správy, overenie podpisu a katalóg udalostí.Registering webhooks, payload shape, signature verification and the event catalogue.

2 min čítania2 min read
  • #webhook
  • #hmac-sha256
  • #x-webhook-signature
  • #udalosti
  • #retry
  • #webhook
  • #hmac-sha256
  • #x-webhook-signature
  • #events
  • #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ý.

Tvar správy

{
  "event": "invoice.delivered",
  "timestamp": "2026-07-13T09:15:03.871Z",
  "data": {
    "invoice_id": "9f2c1e84-3b7a-4d16-b0c5-1e8a72d40f31",
    "mls_c3_status": "OK",
    "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.acceptedOdchádzajúcu faktúru prevzala protistrana (kladná MLS správa). Vtedy sa odpočíta rezervovaný kredit.
invoice.deliveredPri príjemcovi mimo siete Peppol: prijalo sa daňové hlásenie. Pri prichádzajúcej faktúre: prevzali sme dokument.
invoice.rejectedProtistrana faktúru odmietla 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í.

Instead of polling, let the platform call you.

Registration

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 — the platform generates the secret, returned only once
{
  "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"
}

Pass a list of specific events from the catalogue below. The platform generates the signing secret and returns it only in this creation response — it is never shown again in the webhook list. Store it right away; if you lose it, delete the webhook and create a new one.

Payload shape

{
  "event": "invoice.delivered",
  "timestamp": "2026-07-13T09:15:03.871Z",
  "data": {
    "invoice_id": "9f2c1e84-3b7a-4d16-b0c5-1e8a72d40f31",
    "mls_c3_status": "OK",
    "mls_c5_status": null,
    "status": "DELIVERED",
    "selfBilling": false,
    "invoiceTypeCode": "380",
    "document_type": "INVOICE",
    "invoice_number": "2026001",
    "supplier_name": "Supplier Ltd.",
    "buyer_name": "Buyer plc.",
    "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"
  }
}

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

Verifying the signature

The signature is HMAC-SHA256 over the string {timestamp}.{body} using your secret. Always verify it — otherwise anyone can forge an event. Compare with a timing-safe function.

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

export function isValidWebhook(
  rawBody: string,      // the body EXACTLY as received — not re-serialized
  signature: string,    // X-Webhook-Signature
  timestamp: string,    // X-Webhook-Timestamp
  secret: string,
): boolean {
  // Discard anything older than 5 minutes — replay protection.
  // The header is ISO 8601, not epoch: Number() would yield NaN and the
  // comparison would always pass, silently disabling the protection.
  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);
}

Delivery and retries

We wait at most 30 seconds for a response. Return 2xx as soon as you receive the message and process it in the background afterwards. If your endpoint fails or times out, the delivery is queued for a retry.

Retries are signed exactly like the first attempt — same recipe, same header shape. Only the timestamp differs: every attempt carries a fresh one, which is also inside the signed body, so the five-minute window applies to that attempt rather than to the original event. Verification written against the first delivery therefore holds for every retry.

Event catalogue

EventWhen it fires
invoice.acceptedThe other side took the outgoing invoice (positive MLS message). The reserved credit is deducted at that point.
invoice.deliveredFor a recipient outside the Peppol network: the tax report was accepted. For an inbound invoice: we took the document.
invoice.rejectedThe other side rejected the invoice with a negative MLS message — or, for a recipient outside Peppol, the tax authority rejected the tax report.
mls.receivedA tax-reporting response arrived while confirmation from the other side is still pending. The invoice state does not change.