Webhooky

Webhooky vám doručia udalosti z Peppol pipeline (odoslanie, doručenie, zlyhanie, prijatie) 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.

Registrácia endpointu

Endpoint zaregistrujete v portáli v sekcii Webhooky alebo cez POST /v1/partner/webhooks. Pri vytvorení dostanete podpisový secret iba raz — uložte si ho. Jeden partner môže mať najviac 20 endpointov; URL musí byť https://.

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.status_changedZmena stavu prenosu dokladu.
usage.limit_exceededOrganizácia prekročila zahrnutý mesačný objem transakcií.
Eventy participant.activated a participant.failed sú už v katalógu, ale zatiaľ sa neemitujú — pridáme ich bez zmeny formátu payloadu.

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 s exponenciálnym backoffom (5s, 10s, 20s, 40s, 80s) — spolu 6 pokusov.

MechanizmusSprávanie
OpakovaniaIniciálny pokus + 5 retry (5/10/20/40/80 s).
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).

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.