ePodatelna24 — Outbox Ingestion API — príručka pre integráciu ERP

Kompletný návod, ako z ľubovoľného ERP systému (nezáleží na technológii — .NET, Java, PHP, Python, Node…) odovzdať Peppol faktúru do ePodatelna24 na odoslanie do siete Peppol.

Tento repozitár je zároveň referenčná implementácia (Next.js/TypeScript), ale API je obyčajné HTTP + XML — nasledujúci návod stačí na to, aby ste klienta postavili vo svojom jazyku. Všetko, čo tu vidíte, komunikuje výlučne cez verejný HTTP kontrakt: /api-docs (OpenAPI: /api-docs/openapi.yaml).

Tok v skratke

ERP len odovzdá platné XML a dostane potvrdenie o prevzatí. Samotné odoslanie do siete Peppol aj uloženie potvrdenia o doručení už zabezpečí ePodatelna24. Keď chcete vedieť aj to, že faktúra naozaj dorazila, zaregistrujte si webhook — eP24 sa ozve sama.

Vykresľujem diagram…


Obsah

  1. Ako to funguje (model)
  2. Predpoklady
  3. Prostredia a základné URL
  4. Autentifikácia
  5. Kľúčové koncepty
  6. Endpointy
  7. Dátové štruktúry
  8. Stavové kódy a spracovanie chýb
  9. Postup integrácie krok za krokom
  10. Príklady (curl)
  11. Rozhodovacia logika (pseudokód)
  12. Kontrolný zoznam pred spustením
  13. Časté chyby

1. Ako to funguje (model)

ERP odovzdá jednu faktúru (UBL XML). ePodatelna24 ju synchrónne overí, skontroluje odosielateľa aj príjemcu, zarezervuje poplatok za odoslanie a prevezme za ňu zodpovednosť (custody). Jediná odpoveď na požiadavku hovorí, či bola faktúra prijatá.

Model verzie 1 je „odovzdaj a zabudni“:

Keďže je odpoveď jediná spätná väzba, pred vrátením 202 sa skontroluje všetko, čo sa skontrolovať dá:

  1. bezpečnosť XML (žiadne DOCTYPE/entity/XXE),
  2. odosielateľ (DIČ vo faktúre) je v Peppol aktívna spoločnosť pod danou peňaženkou,
  3. faktúra prejde Peppol BIS 3.0 / EN 16931 schematronom (validácia),
  4. príjemca je dosiahnuteľný v sieti Peppol (SMP vyhľadanie),
  5. ochrana proti pokute — faktúra je vystavená dnes (a najviac 15 dní po dátume dodania); staršiu eP24 do Peppolu nepustí a vráti 422 custody_refused s reason: late_issue_guard. Prepínač je na úrovni peňaženky a je zapnutý,
  6. peňaženka pokryje poplatok za odoslanie.

202 teda znamená: „overené, odosielateľ aktívny, príjemca dosiahnuteľný, poplatok zarezervovaný — počítame s doručením."

Po 202 vaša práca končí. Doručenie zabezpečí ePodatelna24 a používateľ ho vidí v nástenke eP24. Ak chcete stav doručenia aj vo svojom ERP, nedopytujte sa — zaregistrujte si webhook a eP24 sa ozve sama, keď faktúra naozaj dorazí (alebo keď odoslanie definitívne zlyhá).


2. Predpoklady


3. Prostredia a základné URL

ProstredieZákladná URLPrefix tokenu
Sandbox (testovanie)https://epodatelna24-sandbox.vercel.appep24api_test_
Produkciahttps://www.epodatelna24.skep24api_prod_

Integráciu vždy najprv postavte a otestujte v sandboxe. Token z jedného prostredia v druhom nefunguje.


4. Autentifikácia

Schéma

Každá požiadavka nesie API token v hlavičke Authorization so schémou Token (rovnaká schéma, akú eP24 používa voči svojmu prístupovému bodu — nie Bearer):

Authorization: Token ep24api_prod_XXXXXXXXXXXXXXXXXXXXXXXX

Ako získať token

V eP24 sa prihláste ako vlastník peňaženky (hlavný správca) a otvorte kartu API prístup. Tá je v každom prostredí inde:

ProstredieKde je karta
ProdukciaNastavenia → API prístup → Nový token
SandboxPeňaženka → API prístup → Nový token

Token sa zobrazí iba raz — ihneď ho bezpečne uložte (na strane eP24 je uložený len jeho hash). Simulátor odkazuje priamo na správne miesto podľa zvoleného prostredia.

Rozsah tokenu

Token je na úrovni peňaženky a platí pre celú peňaženku: jedným tokenom odošlete faktúry za ktorúkoľvek spoločnosť pod danou peňaženkou. Konkrétny odosielateľ sa určí z DIČ vo faktúre — do požiadavky netreba dávať žiadny identifikátor spoločnosti.

Pre účtovnícke firmy: jedna peňaženka spravuje všetkých klientov (spoločnosti) účtovníckej firmy, takže jeden token pokryje všetkých.

Bezpečnostné pravidlá (dôležité)


5. Kľúčové koncepty

5.1 Idempotencia (povinná)

Každé odoslanie musí niesť hlavičku Idempotency-KeyUUID, ktoré vygeneruje a uloží váš ERP ku konkrétnej faktúre.

Prakticky: vo svojej DB majte tabuľku (id_faktúry → idempotency_key, document_id, stav). Kľúč vytvorte raz pri prvom pokuse a používajte ho pri všetkých opakovaniach tej istej faktúry.

5.2 Odosielateľ

Odosielateľ sa určí z DIČ (10 číslic) v bloku AccountingSupplierParty. Poradie je záväzné:

  1. prvý cbc:CompanyID v poradí dokumentu — v bežnej faktúre je to PartyTaxScheme/CompanyID, teda IČ DPH (napr. SK2020123456);
  2. ak jeho hodnota nekončí na 10 číslic, použije sa cbc:EndpointID.

Z víťaznej hodnoty sa berie posledných 10 číslic. Slovenské IČ DPH je SK + DIČ, takže krok 1 vráti DIČ. CompanyID má prednosť pred EndpointID — ak sa líšia, EndpointID sa ignoruje.

Pozor na dlhé identifikátory. PartyLegalEntity/CompanyID nesie IČO (8 číslic) — to je neškodné, lebo neprejde testom na 10 číslic a prepadne sa na EndpointID. Nebezpečný je identifikátor s viac než 10 číslicami, napr. 13-miestny GLN (schemeID="0088"): pravidlo „posledných 10 číslic“ z neho vyrobí neplatné DIČ, ktoré navyše zatieni správne EndpointID. Výsledkom je 403 sender_not_found s DIČ, ktoré ste nikdy nevideli. Uistite sa, že prvý CompanyID v bloku odosielateľa je daňový identifikátor.

Toto DIČ musí zodpovedať aktívnej spoločnosti pod peňaženkou tokenu, inak dostanete 403. Pri samofaktúrach (SelfBilledInvoice alebo CustomizationID so „selfbilling“) je odosielateľom odberateľ (AccountingCustomerParty).

5.3 Príjemca

Príjemca sa určí z AccountingCustomerParty — z cbc:EndpointID s atribútom schemeID, ktorý tvorí Peppol identifikátor schéma:hodnota, napr. 0208:0987654321. Príjemca musí byť registrovaný a dosiahnuteľný v sieti Peppol (overuje sa SMP vyhľadaním), inak dostanete 422 receiver_unreachable.

5.4 Validácia

Faktúra sa validuje voči vendorovanému Peppol BIS 3.0 / EN 16931 schematronu (rovnaké pravidlá ako oficiálny Peppol validátor). Chyby sa vracajú po jednotlivých pravidlách (id pravidla, závažnosť, XPath umiestnenie, popis). Dokument je platný len ak má nula chýb závažnosti error/fatal (varovania neblokujú).

5.5 Limity a formát


6. Endpointy

Základná cesta: /api/v1/outbox.

6.1 POST /api/v1/outbox/documents — odoslať faktúru (prevzatie zodpovednosti)

Overí, prevezme zodpovednosť a zaradí faktúru na odoslanie.

Hlavičky

HlavičkaPovinnáHodnota
AuthorizationánoToken <token>
Content-Typeánoapplication/xml
Idempotency-KeyánoUUID (viď 5.1) — iný tvar vráti 400
X-Requested-DeliverynieLen peppol. email vráti 400 email_delivery_unsupported, iná hodnota 400 bad_requested_delivery
X-Buyer-EmailnieZáložná adresa odberateľa pre človeka vo webe eP24. Nespúšťa e-mailové doručenie; nepoužiteľná hodnota sa ticho zahodí

Telo: surové UBL XML (Invoice alebo CreditNote).

E-mailové doručenie cez API nie je. Od 2026-09-19 API posiela výhradne cez Peppol. X-Requested-Delivery: email kedysi znamenalo „odosielateľ sa s odberateľom dohodol mimo systému“ — rozhodnutie človeka o jednej faktúre, nie niečo, čo má ERP automatizovať pre celú dávku. Hlavičku preto eP24 odmietne, nie ignoruje: potichu poslať cez Peppol niečo, čo si volajúci vyžiadal e-mailom, by bolo horšie než povedať nie. E-mailom sa dá doručiť z webovej aplikácie eP24.

Odpovede

KódVýznamTelo
202Zodpovednosť prevzatáCustodyReceipt
200Idempotentné opakovanie (rovnaký kľúč) — pôvodné potvrdenieCustodyReceipt
400Poškodené/nebezpečné XML, prázdne telo, alebo chýbajúci/neplatný Idempotency-KeyProblem
401Chýbajúci/neplatný tokenProblem
402Peňaženka nepokryje poplatokProblem
403Odosielateľ nie je aktívna spoločnosť pod peňaženkouProblem
409Idempotency-Key použitý s iným telom (porovnáva sa sha256 surových bajtov)Problem
422Custody odmietnutá (napr. ochrana proti pokute) — code: custody_refused, dôvod v poli reasonProblem
413Dokument prekračuje 4 MBProblem
422Validácia zlyhala alebo príjemca nedosiahnuteľnýProblem + errors[]
429Prekročený rate limitProblem + Retry-After
503Prístupový bod dočasne nedostupnýProblem

6.2 POST /api/v1/outbox/documents/validate — nezáväzná validácia (dry-run)

Rovnaké kontroly ako odoslanie, ale bez prevzatia zodpovednosti, bez rezervácie a bez odoslania. Ideálne na testovanie počas integrácie.

Hlavičky: Authorization (povinná), Content-Type: application/xml. Idempotency-Key sa neuplatňuje.

Telo: surové UBL XML.

Odpovede

KódVýznamTelo
200Výsledok validácie (platný aj neplatný dokument)ValidationReport
400Poškodené/nebezpečné XML alebo prázdne teloProblem
401Chýbajúci/neplatný tokenProblem
413Dokument prekračuje 4 MBProblem
429Prekročený rate limitProblem + Retry-After

7. Dátové štruktúry

7.1 CustodyReceipt

Potvrdenie o prevzatí zodpovednosti (odpoveď 202/200). Uložte si documentId na spárovanie s vašou faktúrou.

{
  "documentId": "0b8c7d1e-2f34-4a56-8b9c-1d2e3f4a5b6c", // ID dokumentu v eP24 (uuid)
  "status": "accepted",
  "idempotencyKey": "5980895f-56b6-4a09-a069-e118a146e622",
  "senderDic": "1234567890",
  "receiver": { "scheme": "0208", "value": "0987654321" },
  "documentType": "Invoice",             // "Invoice" | "CreditNote"
  "acceptedAt": "2026-07-08T09:15:06Z",  // ISO 8601 UTC

  // Pribudlo 2026-09; staršie integrácie tieto polia jednoducho ignorujú.
  "requestedDelivery": "peppol",         // vždy "peppol" — iné API neprijme
  "sendStatus": "queued",                // stav odosielania v eP24
  "deliveryChannel": null,               // kanál, keď je už známy
  "custodyAt": "2026-07-08T09:15:06Z",   // kedy bola prevzatá zodpovednosť
  "custodyRefusedReason": null           // vždy null: odmietnutá custody je 422, nie prijatý riadok
}

documentId je identifikátor eP24, nie číslo faktúry — cbc:ID z vášho XML potvrdenie nevracia.

7.2 ValidationReport

Výsledok nezáväznej validácie (odpoveď 200 z /validate).

{
  "valid": false,                         // true len ak errors je prázdne
  "senderDic": "1234567890",              // alebo null
  "senderActive": true,                   // je odosielateľ aktívna spoločnosť pod peňaženkou?
  "receiver": { "scheme": "0208", "value": "0987654321" }, // alebo null
  "receiverReachable": true,              // nájdený v Peppol (SMP)?
  "documentType": "Invoice",              // alebo null
  "errors": [ /* ValidationIssue */ ],
  "warnings": [ /* ValidationIssue */ ]
}

7.3 ValidationIssue

Jedno porušené pravidlo Peppol/EN 16931.

{
  "rule": "PEPPOL-EN16931-R010",          // id pravidla (napr. BR-CO-15, BR-16)
  "severity": "error",                    // "fatal" | "error" | "warning"
  "location": "/Invoice/cac:AccountingSupplierParty/cac:Party/cbc:EndpointID", // XPath alebo null
  "message": "Buyer electronic address MUST be provided."
}

7.4 Problem (RFC 7807)

Chybová odpoveď (application/problem+json). Vetvite podľa poľa code (stabilné, strojovo čitateľné).

{
  "type": "https://www.epodatelna24.sk/errors/insufficient_funds",
  "title": "Insufficient funds",
  "status": 402,
  "code": "insufficient_funds",           // stabilný kód — viď tabuľka nižšie
  "detail": "Wallet balance 0.12 EUR is below the send fee 0.22 EUR."
}

Možné hodnoty code: invalid_xml, unauthorized, insufficient_funds, sender_not_found, sender_not_active, idempotency_conflict, missing_idempotency_key, too_large, validation_failed, receiver_unreachable, rate_limited, upstream_unavailable.

7.5 Problem pri 422

Pri 422 nesie Problem navyše zoznam porušených pravidiel:

{
  "type": "https://www.epodatelna24.sk/errors/validation_failed",
  "title": "Peppol validation failed",
  "status": 422,
  "code": "validation_failed",
  "detail": "The document violates 2 Peppol BIS 3.0 rules.",
  "valid": false,
  "errors": [ /* ValidationIssue */ ],
  "warnings": [ /* ValidationIssue */ ]
}

8. Stavové kódy a spracovanie chýb

KódcodeČo znamenáČo má ERP urobiť
202Zodpovednosť prevzatáUlož documentId. Hotovo.
200Idempotentné opakovanieTúto faktúru si už prijal; použi vrátené documentId.
400invalid_xmlPoškodené/nebezpečné XMLOprav generovanie XML. Neopakuj naslepo.
400missing_idempotency_keyChýba/neplatný Idempotency-KeyPridaj platné UUID.
401unauthorizedZlý/zneplatnený tokenSkontroluj token; prípadne vygeneruj nový.
402insufficient_fundsNedostatok kredituUpozorni používateľa, nech dobije peňaženku; opakuj neskôr (rovnaký kľúč).
403sender_not_foundDIČ odosielateľa nie je pod peňaženkouZaregistruj spoločnosť v eP24.
403sender_not_activeSpoločnosť ešte nie je Peppol-aktívnaPočkaj na aktiváciu, potom opakuj.
413too_large> 4 MBZmenši dokument.
422validation_failedFaktúra porušuje pravidláZobraz errors[], oprav XML, opakuj (rovnaký kľúč).
422receiver_unreachablePríjemca nie je v PeppolOver Peppol identifikátor príjemcu.
422custody_refusedeP24 odmietla prevziať zodpovednosť; dôvod je v poli reason (dnes late_issue_guard)Oprav dôvod — pri late_issue_guard vystav faktúru s dnešným dátumom.
400email_delivery_unsupportedX-Requested-Delivery: emailAPI posiela len cez Peppol; e-mail rieš vo webe eP24.
400bad_requested_deliveryX-Requested-Delivery má inú hodnotu než peppolHlavičku vynechaj alebo pošli peppol.
429rate_limitedPriveľa požiadaviekPočkaj Retry-After sekúnd, opakuj (rovnaký kľúč).
503upstream_unavailableDočasný výpadokExponenciálny backoff, opakuj (rovnaký kľúč).

Pravidlo: 429 a 503 (a 402 po dobití) sú opakovateľné s rovnakým kľúčom. 400/401/403/422 vyžadujú opravu — neopakuj bezo zmeny.


8a. Webhook o doručení — čo príde po 202

Webhooky sú len v produkcii. Sandbox udalosti neodosiela — ani doručovacie, ani inbox.document.received. Prijímač si v ňom otestujete (simulátor aj npm run test:webhook bežia proti vášmu endpointu rovnako), ale skutočná udalosť z eP24 príde až v produkcii. Je to ten istý druh obmedzenia ako chýbajúci SMP: sandboxové 202 nie je dôkaz o doručení a doručenie v sandboxe neoznámi nič.

202 znamená prevzali sme zodpovednosť, nie doručené. Samotné doručenie cez Peppol dobehne o sekundy až minúty neskôr. Aby ste sa nemuseli dopytovať, zaregistrujte si v eP24 (Nastavenia → Notifikácia o doručení (webhook), vedľa API tokenu) jeden HTTPS endpoint na peňaženku a eP24 naň pošle podpísanú JSON udalosť.

8a.1 Tri udalosti

UdalosťKedyVýznam
outbox.document.deliveredprijímací prístupový bod potvrdí prevzatie cez AS4faktúra, ktorú ste odoslali, dorazila na stranu odberateľa
outbox.document.failedodoslanie sa definitívne skončilo neúspechombez zásahu už nedorazí
inbox.document.receivedeP24 dospracuje faktúru adresovanú vámmáte novú prijatú faktúru — UBL je priamo v udalosti (8a.8)

Prvé dve sú o faktúre, ktorú ste odoslali, tretia o faktúre, ktorá prišla vám. Neznáme udalosti (eP24 ich časom pridá) potvrďte a ignorujte — kto ich odmietne, zmení novú funkciu na vypnutý endpoint.

delivered nevzniká vtedy, keď eP24 odovzdá dokument svojmu prístupovému bodu — to je prísľub, nie doručenie. Vzniká až pri potvrdení AS4, čo býva o sekundy až minúty neskôr a občas vôbec.

failed existuje preto, aby ERP čakajúci na delivered nečakal donekonečna. Nesie objekt failure s dôvodom peppol_send_rejected (prístupový bod dokument odmietol) alebo peppol_send_retries_exhausted (eP24 sa vzdala opakovania).

Pre odmietnutie custody udalosť neexistuje — API odpovedalo 422 synchrónne a nič sa neuložilo.

8a.2 Ako požiadavka vyzerá

POST https://erp.example.sk/webhooks/epodatelna24
Content-Type: application/json
User-Agent: ePodatelna24-Webhook/1
X-EP24-Event: outbox.document.delivered
X-EP24-Delivery: 7c1f9a2e-…            ← kľúč idempotencie, rovnaký pri každom opakovaní
X-EP24-Timestamp: 1789459200           ← unixové sekundy
X-EP24-Signature: v1=3b2f…             ← HMAC-SHA256, hex
{
  "schemaVersion": 1,                   // zmena existujúceho poľa príde s bumpom
  "event": "outbox.document.delivered",
  "deliveryId": "7c1f9a2e-…",          // zhodné s X-EP24-Delivery
  "occurredAt": "2026-09-20T09:14:11.482Z",
  "attempt": 1,                         // poradie pokusu o doručenie webhooku
  "document": {
    "id": "0f2c…",                      // documentId z potvrdenia o prevzatí
    "documentNumber": "FV-2026-00128",  // cbc:ID z vašej faktúry
    "clientRequestId": "…",             // Idempotency-Key, ktorý ste poslali, alebo null
    "url": "https://www.epodatelna24.sk/dashboard/documents/0f2c…",  // vyžaduje prihlásenie
    "direction": "sent",
    "sentAt": "2026-09-20T09:12:40.000Z",             // prvé odoslanie cez AS4
    "peppolDeliveredAt": "2026-09-20T09:14:10.900Z",  // potvrdenie
    "senderPeppolId": "0245:2122539001",
    "receiverPeppolId": "0245:2022182030",
    "ionApTransactionId": 60513,        // interné počítadlo ion-AP, NIE AS4 message id
    "currency": "EUR",
    "totalAmount": "1234.56"
  },
  "company":  { "dic": "2122539001", "legalName": "efabox s.r.o." },   // odosielateľ
  "receiver": { "peppolId": "0245:2022182030", "legalName": "…" },     // odberateľ
  // len pri `delivered`:
  "as4": {
    "nonRepudiationReference": "_a09866e6-…",  // podpísal prijímací prístupový bod
    "receiptSha256": "…"                       // hash potvrdenia o prevzatí
  }
  // len pri `failed`:
  // "failure": { "reason": "peppol_send_rejected", "message": "…" }
}

Pri delivered je peppolDeliveredAt vždy vyplnené, pri failed je vždy null. Každé ďalšie pole považujte za možné null a na neznámych poliach nepadajte — eP24 ich bude pridávať. Úplná schéma je v /api-docs ako components.schemas.WebhookEvent.

O peniazoch udalosť nehovorí nič

Poplatky v udalosti nie sú a nebudú: eP24 nechala identifikátory z peňaženky aj sumy vo vnútri eP24, lebo telo webhooku odchádza na endpoint, ktorý neprevádzkuje. Na účtovanie vám ostáva aritmetika, ktorá je jednoznačná:

Ak vám párovanie na riadok bude v prevádzke naozaj chýbať, eP24 to chce riešiť čítacím endpointom na API (rovnaký token peňaženky, riadky za obdobie), nie poľom v udalosti. Ozvite sa im s konkrétnym prípadom.

as4: referencia, ktorú viete použiť

ionApTransactionId je interné počítadlo ion-AP; AS4 eb:MessageId eP24 k dispozícii nemá. To, čo dostanete, je referencia o nepopierateľnosti, ktorú podpísal prijímací prístupový bod, plus hash potvrdenia o prevzatí — tým si udalosť zviažete s dokumentom, ktorý si zákazník stiahne.

(eP24 najprv varovala, že sa referencie skracujú, a vzápätí to odvolala: opakovaná dĺžka je dôsledok pevne daného obsahu potvrdenia, každé uložené potvrdenie končí uzatváracou značkou a referencia je úplná.)

8a.3 Overenie podpisu (HMAC-SHA256)

signature = HMAC-SHA256(secret, "<X-EP24-Timestamp>.<surové telo požiadavky>")

Overujte v tomto poradí a pri akomkoľvek zlyhaní odpovedzte 401:

  1. Najprv časová pečiatka. Odmietnite, ak |teraz − ts| > 300 s. Toto — a nič iné — zastaví prehratie starej, ale pravej udalosti: jej podpis je totiž platný.
  2. Z hlavičky X-EP24-Signature vezmite všetky prvky s prefixom v1= a skúste ich postupne; stačí, ak sedí ktorýkoľvek. Hlavička je zoznam oddelený čiarkami — čo nepoznáte (v2=…), ignorujte. Prvkov v1= môže byť viac: počas rotácie kľúča eP24 podpisuje starým aj novým kľúčom 24 hodín. Kto berie len prvý prvok, prežije ten deň na 401.
  3. Digest počítajte nad surovými bajtmi tela, nie nad znovu serializovaným objektom. Framework, ktorý telo najprv rozparsuje na JSON a potom zloží späť, vyrobí iné bajty a podpis nebude sedieť nikdy.
  4. Porovnávajte v konštantnom čase (hash_equals, CryptographicOperations.FixedTimeEquals, crypto.timingSafeEqual, MessageDigest.isEqual).

Do digestu dávajte reťazec z hlavičky tak, ako prišiel, nie číslo, ktoré ste z neho vyrobili. eP24 posiela celé sekundy bez desatinnej časti a bez vedúcich núl a zaviazala sa to tak nechať — ale "1789459200" a "1789459200.0" sú iné bajty, takže prijímač, ktorý hodnotu preparsuje a naformátuje späť, si zadelal na chybu, ktorá sa bude tváriť ako zlý kľúč.

PHP

<?php
// Surové telo. Nikdy nie json_decode() pred overením — podpis kryje bajty.
$raw = file_get_contents("php://input");
$ts  = $_SERVER["HTTP_X_EP24_TIMESTAMP"] ?? "";
$hdr = $_SERVER["HTTP_X_EP24_SIGNATURE"] ?? "";

// 1) okno ±300 s, ešte pred počítaním digestu
if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300) {
    http_response_code(401);
    exit;
}

// 2) VŠETKY prvky v1=; neznáme (v2=…) ignorujeme. Počas rotácie kľúča
//    prídu dva a stačí, ak sedí ktorýkoľvek.
$provided = [];
foreach (explode(",", $hdr) as $part) {
    $part = trim($part);
    if (str_starts_with($part, "v1=")) { $provided[] = substr($part, 3); }
}
if (!$provided) { http_response_code(401); exit; }

// 3) digest nad surovým telom + 4) porovnanie v konštantnom čase
$expected = hash_hmac("sha256", $ts . "." . $raw, $secret);
$ok = false;
foreach ($provided as $candidate) {
    $ok = hash_equals($expected, $candidate) || $ok;   // bez skratky
}
if (!$ok) { http_response_code(401); exit; }

// Deduplikácia a až potom práca — odpovedzte do 10 sekúnd.
$deliveryId = $_SERVER["HTTP_X_EP24_DELIVERY"] ?? "";
if (!uz_spracovane($deliveryId)) {
    uloz_udalost($deliveryId, json_decode($raw, true));   // v jednej transakcii
}
http_response_code(200);

C# (ASP.NET Core)

app.MapPost("/webhooks/epodatelna24", async (HttpRequest req) =>
{
    // Surové telo. Model binding by ho prečítal a znovu poskladal — podpis by
    // potom nesedel nikdy.
    using var reader = new StreamReader(req.Body);
    var raw = await reader.ReadToEndAsync();

    // 1) okno ±300 s
    var tsHeader = req.Headers["X-EP24-Timestamp"].ToString();
    if (!long.TryParse(tsHeader, out var ts) ||
        Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - ts) > 300)
        return Results.Unauthorized();

    // 2) VŠETKY prvky v1= — počas rotácie kľúča ich príde viac
    var provided = req.Headers["X-EP24-Signature"].ToString()
        .Split(",").Select(p => p.Trim())
        .Where(p => p.StartsWith("v1=")).Select(p => p[3..]).ToArray();
    if (provided.Length == 0) return Results.Unauthorized();

    // 3) digest nad surovým telom
    using var mac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var expected = Convert.ToHexString(
        mac.ComputeHash(Encoding.UTF8.GetBytes($"{tsHeader}.{raw}"))).ToLowerInvariant();

    // 4) konštantný čas — pri rôznej dĺžke vráti false, nevyhodí výnimku
    var expectedBytes = Encoding.UTF8.GetBytes(expected);
    var ok = provided.Aggregate(false, (acc, candidate) =>
        CryptographicOperations.FixedTimeEquals(
            expectedBytes, Encoding.UTF8.GetBytes(candidate)) || acc);
    if (!ok) return Results.Unauthorized();

    var deliveryId = req.Headers["X-EP24-Delivery"].ToString();
    // Deduplikácia + zmena stavu v jednej transakcii, potom hneď 200.
    return Results.Ok();
});

Referenčná implementácia v tomto repozitári: lib/ep24-webhook.ts (overenie), app/api/webhooks/epodatelna24/route.ts (endpoint) a scripts/webhook-selftest.mjs — osem požiadaviek vrátane tých, ktoré musia skončiť na 401.

8a.4 Podpisový kľúč a jeho rotácia

Vznikne pri prvom uložení endpointu, zobrazí sa raz a začína whsec_.

Pri rotácii eP24 podpisuje starým aj novým kľúčom naraz a pošle dva prvky v1= v jednej hlavičke, 24 hodín. Prijímač, ktorý skúša všetky prvky (bod 2 vyššie), prejde rotáciou bez zásahu a bez koordinovaného nasadenia — preto sa oplatí prvky prechádzať, nie brať prvý. Otestujte si to prípadom „rotated (two v1 elements)" v npm run test:webhook: prvý podpis je cudzí, druhý váš, a výsledok musí byť 200.

8a.5 Sémantika doručovania — časť, ktorá hryzie

Odpoveď 401 berie eP24 ako trvalé odmietnutie: tento podpis je zlý. Ak overiť zatiaľ neviete — napríklad vám chýba podpisový kľúč — odpovedzte 503: spýtaj sa ma znova. Rozdiel nie je kozmetický, každá z tých odpovedí ide proti inému prahu vypnutia (vyššie). eP24 túto konvenciu dokumentuje v /api-docs.

8a.6 Tretí stav, ktorý nemá udalosť

Faktúra môže zostať odoslaná bez potvrdenia aj bez zlyhania — odovzdali sme ju, prijímací bod neodpovedal a nič nie je dosť zlé na failed. Dnes v takom prípade nepríde žiadna udalosť. Nestavajte ERP na predpoklade „skôr či neskôr príde delivered alebo failed". Držte si vlastný časový limit (napríklad 24 h) a po jeho uplynutí to ukážte človeku.

8a.7 Vyskúšajte si to v simulátore

Panel „Webhook — prijímacia strana“ na hlavnej stránke prijíma udalosti na /api/webhooks/epodatelna24 a ukazuje tri zoznamy: prijaté udalosti s výsledkom overenia každej požiadavky, prijaté faktúry (inbox) a stav odoslaných dokumentov. Každý zoznam ukáže päť riadkov a ďalšie na kliknutie; tlačidlo Obnoviť načíta stav hneď, Vyčistiť log ho vyprázdni. Keď je karta zatvorená, v jej hlavičke pribudnú počty, takže prijatá faktúra sa nestratí v zabalenej sekcii.

Vlastné udalosti — vrátane zmeneného tela a starej časovej pečiatky, ktoré musia skončiť na 401 — pošle skript npm run test:webhook. Kto nikdy nevidel svoj overovač niečo odmietnuť, nevie, či funguje.

Po odoslaní faktúry sa pod odpoveďou objaví sledovanie práve toho dokumentu: kým nič nepríde, ukazuje „ČAKÁ SA“ a čas od prevzatia; keď udalosť dorazí, prepne sa na „DORUČENÉ“ alebo „ZLYHALO“ a ukáže jej telo. Nemusíte nikam preklikávať ani obnovovať stránku.

Podpisový kľúč dajte nasadeniu ako premennú prostredia EP24_WEBHOOK_SECRET. Pole na jeho vloženie sa v paneli ukáže len vtedy, keď nasadenie žiadny kľúč nemá — inak by ho na verejnom simulátore mohol ktokoľvek prepísať a všetky skutočné doručenia by začali končiť na 401. Kľúč v každom prípade zostáva na serveri, prehliadač ho nikdy nedostane. To isté z príkazového riadka:

EP24_WEBHOOK_SECRET=whsec_… npm run test:webhook -- https://vas-simulator.example

Krátka stránka o tom, čo sa pri prijímaní webhooku kazí najčastejšie (surové telo a deduplikácia), je v simulátore na ceste /webhook.

Kde si simulátor udalosti pamätá

Bez konfigurácie ide všetko do pamäte inštancie. Lokálne to stačí, na serverless nasadení nie: požiadavku od eP24 obslúži jedna inštancia a panel vykresľuje iná, takže korektne prijatá udalosť sa v paneli neobjaví (a nečinná inštancia sa aj tak recykluje aj s obsahom).

Ak má panel ukazovať skutočné doručenia, dajte nasadeniu zdieľané úložisko — Redis cez REST, buď z integrácie Upstash vo Verceli, alebo z vlastnej databázy:

KV_REST_API_URL=https://…              # alebo UPSTASH_REDIS_REST_URL
KV_REST_API_TOKEN=…                    # alebo UPSTASH_REDIS_REST_TOKEN

Či nasadenie úložisko naozaj vidí, zistíte bez prihlásenia — otvorte adresu endpointu v prehliadači. Odpovie 405 (POST-only) a v poli ready povie, či má podpisový kľúč a ktoré úložisko používa:

{
  "error": "method_not_allowed",
  "ready": { "secretConfigured": true, "storage": "redis", "note": "…" }
}

Keď panel beží bez zdieľaného úložiska, napíše to v pätičke — prázdny zoznam sa tak nedá zameniť s nedoručenou udalosťou. Záznamy vydržia 24 hodín, kľúče proti duplicitám 7 dní (eP24 opakuje ~9 hodín, kratšia platnosť by pustila duplikát).

Skutočný ERP ukladá X-EP24-Delivery do databázy v tej istej transakcii ako zmenu stavu; až to robí spracovanie idempotentným.

8a.8 Prijaté faktúry — inbox.document.received

Tretia udalosť ide opačným smerom: faktúra prišla vám. eP24 ju pošle vtedy, keď jej worker dospracuje prijatý dokument a ten je k dispozícii — v tej istej chvíli, keď o ňom e-mailom informuje ľudí. Nie vtedy, keď ju prístupový bod odovzdá eP24: dovtedy neexistuje ani XML, ani metadáta. Dokument, ktorý spracovanie nedokončí, udalosť nevyvolá.

Vykresľujem diagram…

Rozsah je každá firma účtovaná na peňaženke, ktorej patrí endpoint — rovnako ako pri doručovacích udalostiach. Deduplikácia je na dokument: jedna udalosť na dokument a endpoint, navždy. Opakované spracovanie nevyrobí novú.

V Nastavenia → Notifikácia o doručení je prepínač „Aj prijaté faktúry“ (predvolene zapnutý), takže peňaženka, ktorej ERP faktúry iba odosiela, sa tejto prevádzky môže vzdať bez straty doručovacích udalostí.

XML je priamo v udalosti

{
  "schemaVersion": 1,
  "event": "inbox.document.received",
  "deliveryId": "…",
  "attempt": 1,
  "occurredAt": "2026-09-20T07:12:03.441Z",

  "received": {                          // ReceivedDocumentDetailResponse (§8.2)
    "metadata": {
      "documentId": "INV-2026-0001",     // cbc:ID faktúry
      "documentTypeId": "urn:…Invoice-2::Invoice##…",
      "processId": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
      "senderParticipantId": "0245:1234567890",
      "receiverParticipantId": "0245:9876543210",
      "creationDateTime": "2026-02-12T10:30:00Z"
    },
    "payload": "<?xml version=\"1.0\"…",  // UBL ako JSON reťazec, NIE base64
    "payloadFormat": "XML"
  },

  "payloadSha256": "…",                  // mimo §8.2 objektu, aby sa jeho tvar nemenil
  "payloadBytes": 4419,
  "company":  { "dic": "9876543210", "legalName": "…" },
  "supplier": { "name": "ABC s.r.o.", "taxId": "1234567890" },
  "document": { "id": "…", "url": "https://www.epodatelna24.sk/dashboard/inbox/…" }
}

Dokument cestuje v tele udalosti zámerne: sumárne metadáta eP24 sú stavané na vykreslenie e-mailu (riadok je { name, amount } bez množstva, jednotkovej ceny aj sadzby DPH), takže z nich sa korektný záväzok zaúčtovať nedá. UBL je jediný účtovne použiteľný artefakt.

Tri veci, ktoré si ustrážte

  1. payload môže byť null. Buď dokument prekročil limit pre vloženie, alebo ho eP24 nevedela načítať; dôvod je v payloadOmittedReason. Metadáta aj document.url prídu tak či tak, takže faktúru nezahoďte — ukážte ju človeku. Toto je vetva, na ktorej integrácie ticho strácajú faktúry.

  2. Overte payloadSha256. Prepočítajte sha256 nad received.payload v UTF-8 a porovnajte. Podpis hlavičky dokazuje, že udalosť dorazila nezmenená; tento digest dokazuje, že XML vnútri je ten dokument, ktorý eP24 myslela. Keď nesedí, udalosť potvrďte (2xx — bajty boli podpísané, opakovanie nič nezmení), ale faktúru neúčtujte bez overenia.

  3. Zdvihnite limit na veľkosť tela. eP24 garantuje, že žiadna požiadavka neprekročí 4 MB — meria sa serializované telo udalosti aj s obálkou, takže nič neodpočítavate. Dokument, ktorý by cez limit prešiel až s obálkou, príde s payload: null. Medián je 4,4 kB, 95. percentil 90 kB, najväčší doteraz 373 kB, ale faktúry s prílohami (base64 v AdditionalDocumentReference) idú výrazne vyššie.

    Predvolené limity, ktoré vás zastavia skôr než eP24:

    ProstrediePredvolenéPri 4 MB
    express.json()100 kBmusíte zdvihnúť
    Next.js Server Actions1 MBroute handler limit nemá
    Django DATA_UPLOAD_MAX_MEMORY_SIZE2,5 MBmusíte zdvihnúť
    PHP post_max_size8 MBstačí
    Kestrel / ASP.NET Core~30 MBstačí

    Pozor na Express: 100 kB je nad 95. percentilom, ale len o desatinu — prvá faktúra s prílohou tú rezervu minie. Vzniknuté 413 vaše ani ich opakovania nevyriešia, takže o faktúru prídete bez akejkoľvek chybovej hlášky. A ak hostujete na Verceli, platí ešte platformový strop: telo nad ~4,5 MB odmietne ešte pred vaším kódom obyčajným textom FUNCTION_PAYLOAD_TOO_LARGE (nie Problem) — 4 MB od eP24 sa pod neho zmestí aj s rezervou.

Prijaté faktúry v simulátore

Panel má sekciu Prijaté faktúry (inbox): pri každej uvidíte výsledok kontroly digestu — XML OVERENÉ, XML NESEDÍ, alebo BEZ XML aj s dôvodom — a začiatok prijatého XML. Log si celé XML neukladá (30 záznamov po megabajtoch nie je log), drží len obálku a ukážku začiatku dokumentu.

Vlastné udalosti si proti endpointu pošlete skriptom npm run test:webhook (dvanásť požiadaviek vrátane tých, ktoré musia skončiť na 401).


9. Postup integrácie krok za krokom

  1. Vygeneruj token v eP24 (sandbox) a ulož ho bezpečne na serveri.
  2. Priprav UBL XML faktúry (Peppol BIS 3.0). Uisti sa, že DIČ odosielateľa a Peppol identifikátor príjemcu sú správne (viď 5.2, 5.3).
  3. (Voliteľné, odporúčané počas vývoja) zavolaj POST …/documents/validate a skontroluj valid, senderActive, receiverReachable. Oprav chyby z errors[].
  4. Vygeneruj Idempotency-Key (UUID) a ulož ho vo svojej DB spolu s ID faktúry (stav = pending).
  5. Zavolaj POST …/documents s hlavičkami Authorization, Content-Type: application/xml, Idempotency-Key a telom = XML. Timeout klienta ~60 s.
  6. Vetvi podľa stavu (tabuľka v kap. 8):
    • 202/200 → ulož documentId, stav = accepted. Hotovo.
    • 429/503/402 → naplánuj opakovanie s rovnakým kľúčom (backoff).
    • 422 → zobraz chyby, oprav faktúru, opakuj s rovnakým kľúčom.
    • 400/401/403 → chyba konfigurácie/dát; vyrieš a až potom opakuj.
  7. Doručenie sleduj webhookom, nie dopytovaním. Po 202 je faktúra v rukách eP24. Ak stav potrebuješ aj v ERP, zaregistruj webhook — príde outbox.document.delivered, prípadne outbox.document.failed. Bez neho stav vidí používateľ v nástenke eP24.

Tip na spoľahlivosť: ak vám klient spadne po odoslaní a neviete, či požiadavka prešla, jednoducho ju zopakujte s rovnakým kľúčom — dostanete buď pôvodné 200, alebo (ak sa prvý pokus vôbec nespracoval) čerstvé 202.


10. Príklady (curl)

Nezáväzná validácia

curl -X POST "https://epodatelna24-sandbox.vercel.app/api/v1/outbox/documents/validate" \
  -H "Authorization: Token ep24api_test_XXXXXXXX" \
  -H "Content-Type: application/xml" \
  --data-binary @invoice.xml

Odoslanie (prevzatie zodpovednosti)

curl -X POST "https://epodatelna24-sandbox.vercel.app/api/v1/outbox/documents" \
  -H "Authorization: Token ep24api_test_XXXXXXXX" \
  -H "Content-Type: application/xml" \
  -H "Idempotency-Key: 5980895f-56b6-4a09-a069-e118a146e622" \
  --data-binary @invoice.xml

Úspech (202):

{ "documentId": "0b8c7d1e-…", "status": "accepted", "senderDic": "1234567890",
  "receiver": { "scheme": "0208", "value": "0987654321" },
  "documentType": "Invoice", "acceptedAt": "2026-07-08T09:15:06Z" }

Neplatná faktúra (422):

{ "code": "validation_failed", "status": 422,
  "detail": "The document violates 1 Peppol BIS 3.0 rule(s).",
  "valid": false,
  "errors": [ { "rule": "BR-16", "severity": "error",
    "location": "/Invoice/cac:InvoiceLine",
    "message": "An Invoice must have at least one Invoice line." } ] }

Bezpečné opakovanie (rovnaký kľúč → 200)

# rovnaká faktúra, rovnaký Idempotency-Key → pôvodné potvrdenie, nie druhé odoslanie
curl -X POST "https://epodatelna24-sandbox.vercel.app/api/v1/outbox/documents" \
  -H "Authorization: Token ep24api_test_XXXXXXXX" \
  -H "Content-Type: application/xml" \
  -H "Idempotency-Key: 5980895f-56b6-4a09-a069-e118a146e622" \
  --data-binary @invoice.xml

11. Rozhodovacia logika (pseudokód)

function odosli(faktura):
    kluc = faktura.idempotency_key or nove_uuid()   # vytvor raz, ulož
    uloz(faktura.id, kluc, stav="pending")

    odpoved = HTTP_POST(base + "/api/v1/outbox/documents",
                        headers = { Authorization: "Token " + TOKEN,
                                    "Content-Type": "application/xml",
                                    "Idempotency-Key": kluc },
                        body = faktura.xml,
                        timeout = 60s)

    switch odpoved.status:
        case 202, 200:
            uloz_document_id(faktura.id, odpoved.body.documentId, stav="accepted")
            return HOTOVO
        case 422:
            zobraz_chyby(odpoved.body.errors)        # oprav XML, potom odosli() znova (rovnaký kľúč)
            return OPRAV
        case 429, 503:
            pockaj(odpoved.headers["Retry-After"] or backoff())
            return odosli(faktura)                    # rovnaký kľúč
        case 402:
            upozorni_dobit_penazenku()                # opakuj neskôr, rovnaký kľúč
            return CAKA
        case 400, 401, 403:
            zaloguj_chybu(odpoved.body.code, odpoved.body.detail)  # vyrieš konfiguráciu/dáta
            return CHYBA

Hotová referenčná implementácia tejto logiky je v lib/ep24-client.ts (funkcie submitDocument, validateDocument, classifySubmit). Serverový proxy (vzor „backend ERP volá API") je v app/actions.ts.

Generovanie klienta: OpenAPI špecifikácia je na …/api-docs/openapi.yaml. Vo väčšine jazykov z nej viete vygenerovať typového klienta (napr. openapi-generator, NSwag pre .NET, openapi-python-client).


12. Kontrolný zoznam pred spustením


12a. Prostredia v simulátore

Simulátor má prepínač Sandbox / Produkcia. Pri každom prostredí zobrazí odkaz na vytvorenie tokenu (v produkcii Nastavenia → API prístup, v sandboxe Peňaženka → API prístup) a očakávaný prefix; ak token prefixu nezodpovedá, upozorní ešte pred odoslaním (token z iného prostredia vráti 401).

ProstredieZákladná URLTokenVytvorenie tokenu
Sandboxhttps://epodatelna24-sandbox.vercel.appep24api_test_/dashboard/wallet
Produkciahttps://www.epodatelna24.skep24api_prod_/dashboard/settings

Čo sa dá v paneli „Požiadavka“ nastaviť

Po odoslaní sa pod odpoveďou ukáže sledovanie doručenia práve tohto dokumentu (viď 8a.7).

Poistky v produkcii

V produkcii sa platná faktúra reálne doručí cez sieť Peppol, prevezme sa zodpovednosť a peňaženka sa zaťaží poplatkom — nedá sa to vziať späť. Preto:


12b. Testovacie scenáre v simulátore

Simulátor obsahuje rozbaľovací výber „Testovací scenár“, ktorý načíta vzorové XML (public/invoice.xml a public/samples/) a — pri výsledkoch riadených hlavičkou alebo veľkosťou — pri odoslaní upraví požiadavku tak, aby priviedla presne k danému kódu. Vzory používajú existujúceho odosielateľa Kaviareň Luna s.r.o., DIČ 0072656311 a odberateľa Stavebniny Tatra a.s., DIČ 0072656312 — obe sú firmy vyhradeného sandbox tenanta (okrem scenára sender_not_found, ktorý má zámerne neregistrované DIČ). Pri každom scenári sa zobrazí očakávaný stav, code a miera reprodukovateľnosti:

Všetky vzory vychádzajú z jednej testovacej faktúry eP24 (vrátane dvoch príloh), líšia sa vždy jedinou mutáciou a sú overené skutočným schematronom (npm run validate:fixtures), nie tým, že ich sandbox prijal.

Dátumy sa pri načítaní prepíšu na dnešok. Ochrana proti pokute prepúšťa do Peppolu len faktúru vystavenú dnes (a najviac 15 dní po dátume dodania), takže vzor s pevným dátumom by bol o deň neskôr zadržaný. Simulátor preto pri načítaní nastaví IssueDate a DueDate na dnešok a TaxPointDate s ActualDeliveryDate na včerajšok — overené je oboje, súbor aj prepísaná podoba.

ScenárVýsledokReprodukovateľnosťAko vzniká
Prijaté202závisí od peňaženkyplatné XML + váš aktívny DIČ + dosiahnuteľný príjemca
Idempotentné opakovanie200závisí od peňaženky202, potom to isté odoslať s rovnakým kľúčom
invalid_xml400deterministickételo nie je well-formed XML
missing_idempotency_key400deterministickéodoslanie bez Idempotency-Key
email_delivery_unsupported400deterministickéX-Requested-Delivery: email — API posiela len cez Peppol
unauthorized401deterministickénesprávny token
insufficient_funds402vynútené v SandboxeX-Ep24-Simulate: insufficient_funds
sender_not_found403deterministickéDIČ dodávateľa nie je pod peňaženkou
sender_not_active403vynútené v SandboxeX-Ep24-Simulate: sender_not_active
idempotency_conflict409závisí od peňaženkyrovnaký kľúč, bajtovo iné telo (najprv treba 202)
too_large413deterministickételo sa pri odoslaní nafúkne nad limit
validation_failed422deterministickéchýba el. adresa odberateľa (PEPPOL-EN16931-R010)
validation_failed (súčty)422deterministickésplatná suma nesedí so súčtom (BR-CO-16)
receiver_unreachable422v sandboxe nereprodukovateľnésandbox nemá SMP — vyžaduje simulate trigger
rate_limited429vynútené v SandboxeX-Ep24-Simulate: rate_limited (reálny limit je per-inštancia)
upstream_unavailable503vynútené v SandboxeX-Ep24-Simulate: upstream_unavailable

Scenáre označené „deterministické“ dajú vždy rovnaký výsledok.

Sandbox vie výsledky vynútiť. Sandbox prijíma hlavičku X-Ep24-Simulate: insufficient_funds | sender_not_active | upstream_unavailable | rate_limited, ktorá vynúti daný výsledok. Simulátor ju pri týchto štyroch scenároch posiela automaticky — takže v Sandboxe dostanete skutočnú odpoveď zo servera, nie lokálnu ukážku. Požiadavka najprv prejde bežnou kontrolou (auth, kľúč, telo) a odpoveď nesie hlavičku X-Ep24-Simulated. V produkcii sa hlavička vôbec nečíta, preto tam ostávajú tieto scenáre nevynútiteľné a odoslanie je pri nich vypnuté.

Pozor: openapi.yaml na sandboxe a v produkcii sa líšia — sandbox dokumentuje X-Ep24-Simulate, produkcia nie. Detaily v MEMO_REPLY.md.

Sandbox neposiela webhooky. Doručovacie udalosti ani prijaté faktúry v sandboxe nechodia — panel „Webhook — prijímacia strana“ aj sledovanie pod odpoveďou to v sandboxe rovno napíšu a nečakajú na udalosť, ktorá nepríde. Samotný prijímač funguje, otestujete ho skriptom npm run test:webhook.

Sandbox nemá register účastníkov (SMP). Mock vracia exists:true pre ľubovoľný identifikátor, takže dosiahnuteľnosť príjemcu sa nevyhodnocuje a 422 receiver_unreachable sa v sandboxe nedá vyvolať vôbec: bez schemeID poruší XML pravidlo BR-63 a validácia (ktorá beží skôr) vráti validation_failed; s platným identifikátorom prejde ako 202. Dôsledok: sandboxové 202 nie je dôkaz, že faktúra prejde v produkcii. Vzory si overujte priamo voči schematronu — npm run validate:fixtures.

Dve rôzne 413: nad ~4,5 MB odmietne telo platforma pred autentifikáciou (text/plain); 4–4,5 MB s platným tokenom vráti API vlastné 413 s telom Problem až po autentifikácii. Pri 413 preto vždy kontrolujte Content-Type, než telo parsujete ako JSON.

Štyri výsledky sa nedajú vynútiť samotnou požiadavkouinsufficient_funds (402), sender_not_active (403), rate_limited (429) a upstream_unavailable (503); v Sandboxe ich ale vynúti hlavička X-Ep24-Simulate, takže tam odoslanie zapnuté je. Piaty, receiver_unreachable (422), sa nedá vyvolať ani tak — viď poznámka o chýbajúcom SMP vyššie. Ich vzor je platná faktúra s aktívnym odosielateľom, takže skutočné odoslanie by skončilo ako 202 (prijaté) a prevzalo zodpovednosť — čo vyzerá ako falošný úspech. Preto je pri nich tlačidlo „Odoslať“ vypnuté a namiesto neho ponúkame „Zobraziť ukážkovú odpoveď“: telo Problem (RFC 7807) sa vykreslí lokálne, zreteľne označené ako SIMULOVANÉ, aby si ERP vývojár pozrel presný tvar odpovede, na ktorý má vetviť. Tlačidlo „Validovať“ zostáva aktívne — je nezáväzné a pri sender_not_active v poli senderActive priamo uvidíte stav odosielateľa.


12c. Demo firmy vo vyhradenom sandbox tenante

Sandbox vyhradzuje pre simulátor pevný tenant. Voči nemu je nasledujúcich šesť DIČ konštantných — po každom POST /api/sandbox/reset sa obnovia bajt po bajte a sú stabilné aj naprieč nasadeniami.

DIČObchodné menoDoručovacia adresa
0072656311Kaviareň Luna s.r.o.demo+007265631-1@sandbox.epodatelna24.sk
0072656312Stavebniny Tatra a.s.demo+007265631-2@sandbox.epodatelna24.sk
0072656313Pekáreň Zlatý klas s.r.o.demo+007265631-3@sandbox.epodatelna24.sk
0072656314IT Solutions Východ s.r.o.demo+007265631-4@sandbox.epodatelna24.sk
0072656315Autoservis Rapid s.r.o.demo+007265631-5@sandbox.epodatelna24.sk
0072656316Účtovníctvo Profit s.r.o.demo+007265631-6@sandbox.epodatelna24.sk

Vlastná fakturačná identita eP24 (rovnaká pre každý tenant): IČO 54000111, DIČ 2120000111.

Pravidlá

  1. Hardcodujte len týchto šesť hodnôt. DIČ nikdy negenerujte — companies.dic je not null unique check (dic ~ '^\d{10}$'), takže neznáme DIČ nerozpozná žiadnu spoločnosť a kolidujúce zlyhá.
  2. Len pod vyhradeným uid. Iná relácia (aj anonymná) dostane namespace odvodený z hashtext(uid) a tieto DIČ mať nebude.
  3. Maximálne deväť klientov — DIČ je 9 číslic namespace + jednociferný index.
  4. Reset je idempotentný, medzi scenármi ho môžete volať voľne.
  5. Spoločnosti nevytvárajte priamo — seedujte cez sandbox, nech ostanú konzistentné väzby na členstvo, peňaženku a doručovanie.

Od 2026-09-20 sú živé — a v inom namespace, než hovorilo memo: tenant bol naseedovaný ako 007265631x, nie 999999999x. Všetkých šesť je Peppol-aktívnych, takže vzory v simulátore posielajú z jednej demo firmy druhej; odoslaná faktúra sa tým pádom vráti do tej istej peňaženky ako inbox.document.received.

Pozor na 00726563170072656319. Sú to platné indexy klientov 7–9 vo vyhradenom namespace, dnes neobsadené. Vzor pre sender_not_found preto nepoužíva žiadne z nich, ale 8888888888 — inak by po doseedovaní troch klientov namiesto 403 vrátil skutočné 202.



13. Časté chyby


Pre tím, ktorý stavia serverovú stranu: brief so všetkým, čo klient od API očakáva — kontrakt, idempotencia, webhooky a pravidlá, ktoré musia platiť — je v public/ep24-server-brief.md a na hlavnej stránke simulátora sa dá stiahnuť jedným kliknutím. Je písaný tak, aby sa dal dať priamo AI agentovi ako vstup.

Referencie: interaktívna dokumentácia a OpenAPI — /api-docs · /api-docs/openapi.yaml

Otázky → tím ePodatelna24.