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
- Ako to funguje (model)
- Predpoklady
- Prostredia a základné URL
- Autentifikácia
- Kľúčové koncepty
- Endpointy
- Dátové štruktúry
- Stavové kódy a spracovanie chýb
- Postup integrácie krok za krokom
- Príklady (curl)
- Rozhodovacia logika (pseudokód)
- Kontrolný zoznam pred spustením
- Č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“:
202= prevzali sme zodpovednosť a faktúru odošleme. ERP už nemusí nič sledovať.4xx= faktúra bola odmietnutá (dôvod je v tele odpovede).
Keďže je odpoveď jediná spätná väzba, pred vrátením 202 sa skontroluje
všetko, čo sa skontrolovať dá:
- bezpečnosť XML (žiadne DOCTYPE/entity/XXE),
- odosielateľ (DIČ vo faktúre) je v Peppol aktívna spoločnosť pod danou peňaženkou,
- faktúra prejde Peppol BIS 3.0 / EN 16931 schematronom (validácia),
- príjemca je dosiahnuteľný v sieti Peppol (SMP vyhľadanie),
- 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_refusedsreason: late_issue_guard. Prepínač je na úrovni peňaženky a je zapnutý, - 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
- API token vygenerovaný v eP24 (viď Autentifikácia).
- Odosielajúca spoločnosť (podľa DIČ vo faktúre) je v eP24 registrovaná a Peppol-aktivovaná, a patrí pod peňaženku, ku ktorej token patrí.
- Peňaženka má dostatok kreditu na poplatok za odoslanie.
- Faktúra je platné UBL 2.1 (
InvoicealeboCreditNote) podľa profilu Peppol BIS Billing 3.0.
3. Prostredia a základné URL
| Prostredie | Základná URL | Prefix tokenu |
|---|---|---|
| Sandbox (testovanie) | https://epodatelna24-sandbox.vercel.app | ep24api_test_ |
| Produkcia | https://www.epodatelna24.sk | ep24api_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:
| Prostredie | Kde je karta |
|---|---|
| Produkcia | Nastavenia → API prístup → Nový token |
| Sandbox | Peň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é)
- API je server-to-server. Volajte ho z backendu svojho ERP, nikdy z prehliadača. API nemá CORS a token sa nesmie dostať do prehliadača.
- Token považujte za tajomstvo (ako heslo). Neukladajte ho do klientskej aplikácie, logov ani do verzovacieho systému.
- Kompromitovaný token zneplatnite v eP24 na tej istej karte API prístup (v produkcii pod Nastavenia, v sandboxe pod Peňaženka) a vygenerujte nový.
5. Kľúčové koncepty
5.1 Idempotencia (povinná)
Každé odoslanie musí niesť hlavičku Idempotency-Key — UUID, ktoré
vygeneruje a uloží váš ERP ku konkrétnej faktúre.
- Nový kľúč = nová faktúra.
- Rovnaký kľúč = bezpečné opakovanie. Ak odoslanie zopakujete s rovnakým kľúčom (napr. po timeoute), dostanete pôvodné potvrdenie namiesto druhého odoslania.
- Kľúč sa „zaviaže“ až pri úspešnom
202. Zamietnutie (4xx) kľúč nespotrebuje — po oprave a opätovnom odoslaní tej istej faktúry použite rovnaký kľúč. (Aj402kľúč uvoľní: po dobití peňaženky zopakujte s rovnakým kľúčom a dostanete čerstvé202, nie opakovanie.) - Režim je prísny. Rovnaký kľúč s bajtovo iným telom vráti
409 idempotency_conflict— nič sa neuloží, nič sa neúčtuje a pôvodný dokument zostáva nedotknutý. Zhoda sa určuje zsha256surových bajtov požiadavky, takže stačí jediná zmenená medzera. Kľúče nikdy nerecyklujte medzi dokumentmi. - Opakovanie sa validuje znova. Zopakovaná požiadavka prejde celou vstupnou
kontrolou (XML, odosielateľ, jeho aktivácia, schematron, dosiahnuteľnosť
príjemcu), takže môže vrátiť
4xxaj vtedy, keď pôvodné odoslanie uspelo. Nevyhodnocuje sa len zostatok peňaženky —402preto pri opakovaní nikdy nenastane.
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é:
- prvý
cbc:CompanyIDv poradí dokumentu — v bežnej faktúre je toPartyTaxScheme/CompanyID, teda IČ DPH (napr.SK2020123456); - 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/CompanyIDnesie IČO (8 číslic) — to je neškodné, lebo neprejde testom na 10 číslic a prepadne sa naEndpointID. 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ávneEndpointID. Výsledkom je403 sender_not_founds DIČ, ktoré ste nikdy nevideli. Uistite sa, že prvýCompanyIDv 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
- Content-Type:
application/xml, kódovanie UTF-8, telo = surové UBL XML. - Maximálna veľkosť tela: 4 MB (
OUTBOUND_UBL_FUNCTION_MAX_BYTES). Nad ~4,5 MB telo odmietne platforma ešte pred vaším kódom (obyčajný text, nieProblem). - Rate limit: cca 120 požiadaviek/min na token (
429+Retry-After). - Timeout klienta: validácia + SMP + odoslanie môžu trvať niekoľko sekúnd — nastavte timeout HTTP klienta veľkoryso (napr. 60 s).
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čka | Povinná | Hodnota |
|---|---|---|
Authorization | áno | Token <token> |
Content-Type | áno | application/xml |
Idempotency-Key | áno | UUID (viď 5.1) — iný tvar vráti 400 |
X-Requested-Delivery | nie | Len peppol. email vráti 400 email_delivery_unsupported, iná hodnota 400 bad_requested_delivery |
X-Buyer-Email | nie | Zá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: emailkedysi 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ód | Význam | Telo |
|---|---|---|
202 | Zodpovednosť prevzatá | CustodyReceipt |
200 | Idempotentné opakovanie (rovnaký kľúč) — pôvodné potvrdenie | CustodyReceipt |
400 | Poškodené/nebezpečné XML, prázdne telo, alebo chýbajúci/neplatný Idempotency-Key | Problem |
401 | Chýbajúci/neplatný token | Problem |
402 | Peňaženka nepokryje poplatok | Problem |
403 | Odosielateľ nie je aktívna spoločnosť pod peňaženkou | Problem |
409 | Idempotency-Key použitý s iným telom (porovnáva sa sha256 surových bajtov) | Problem |
422 | Custody odmietnutá (napr. ochrana proti pokute) — code: custody_refused, dôvod v poli reason | Problem |
413 | Dokument prekračuje 4 MB | Problem |
422 | Validácia zlyhala alebo príjemca nedosiahnuteľný | Problem + errors[] |
429 | Prekročený rate limit | Problem + Retry-After |
503 | Prí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ód | Význam | Telo |
|---|---|---|
200 | Výsledok validácie (platný aj neplatný dokument) | ValidationReport |
400 | Poškodené/nebezpečné XML alebo prázdne telo | Problem |
401 | Chýbajúci/neplatný token | Problem |
413 | Dokument prekračuje 4 MB | Problem |
429 | Prekročený rate limit | Problem + 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ód | code | Čo znamená | Čo má ERP urobiť |
|---|---|---|---|
202 | — | Zodpovednosť prevzatá | Ulož documentId. Hotovo. |
200 | — | Idempotentné opakovanie | Túto faktúru si už prijal; použi vrátené documentId. |
400 | invalid_xml | Poškodené/nebezpečné XML | Oprav generovanie XML. Neopakuj naslepo. |
400 | missing_idempotency_key | Chýba/neplatný Idempotency-Key | Pridaj platné UUID. |
401 | unauthorized | Zlý/zneplatnený token | Skontroluj token; prípadne vygeneruj nový. |
402 | insufficient_funds | Nedostatok kreditu | Upozorni používateľa, nech dobije peňaženku; opakuj neskôr (rovnaký kľúč). |
403 | sender_not_found | DIČ odosielateľa nie je pod peňaženkou | Zaregistruj spoločnosť v eP24. |
403 | sender_not_active | Spoločnosť ešte nie je Peppol-aktívna | Počkaj na aktiváciu, potom opakuj. |
413 | too_large | > 4 MB | Zmenši dokument. |
422 | validation_failed | Faktúra porušuje pravidlá | Zobraz errors[], oprav XML, opakuj (rovnaký kľúč). |
422 | receiver_unreachable | Príjemca nie je v Peppol | Over Peppol identifikátor príjemcu. |
422 | custody_refused | eP24 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. |
400 | email_delivery_unsupported | X-Requested-Delivery: email | API posiela len cez Peppol; e-mail rieš vo webe eP24. |
400 | bad_requested_delivery | X-Requested-Delivery má inú hodnotu než peppol | Hlavičku vynechaj alebo pošli peppol. |
429 | rate_limited | Priveľa požiadaviek | Počkaj Retry-After sekúnd, opakuj (rovnaký kľúč). |
503 | upstream_unavailable | Dočasný výpadok | Exponenciá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 ajnpm run test:webhookbež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é202nie 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ť | Kedy | Význam |
|---|---|---|
outbox.document.delivered | prijímací prístupový bod potvrdí prevzatie cez AS4 | faktúra, ktorú ste odoslali, dorazila na stranu odberateľa |
outbox.document.failed | odoslanie sa definitívne skončilo neúspechom | bez zásahu už nedorazí |
inbox.document.received | eP24 dospracuje faktúru adresovanú vám | má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á:
- jedno
delivered= práve jeden poplatok za cenu platnú v tej chvíli, failed= žiadny poplatok; eP24 účtuje až pri doručení, takže neúspešné odoslanie nebolo nikdy zaťažené a niet čo refundovať,- konkrétny riadok peňaženky viete nájsť len vo výpise v portáli — je to cesta pre človeka, nie pre integráciu.
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:
- Najprv časová pečiatka. Odmietnite, ak
|teraz − ts| > 300s. Toto — a nič iné — zastaví prehratie starej, ale pravej udalosti: jej podpis je totiž platný. - Z hlavičky
X-EP24-Signaturevezmite všetky prvky s prefixomv1=a skúste ich postupne; stačí, ak sedí ktorýkoľvek. Hlavička je zoznam oddelený čiarkami — čo nepoznáte (v2=…), ignorujte. Prvkovv1=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ň na401. - 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.
- 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
- Aspoň raz. Tá istá udalosť môže prísť dvakrát a časový limit po tom, čo ste
ju už uložili, je pre eP24 stále neúspech.
X-EP24-Deliveryje pri každom opakovaní rovnaké: uložte ho a spracovanie urobte idempotentným. Toto je najdôležitejší riadok celej kapitoly. - Jedna udalosť každého druhu na dokument.
X-EP24-Deliveryje jedinečné na udalosť, nie na dokument:delivereda neskoršiefailedtoho istého dokumentu majú rôzne id a obe treba spracovať. Druhé zlyhanie toho istého dokumentu už ale žiadnu udalosť nevyvolá — stav sa nemení, takže niet čo oznamovať. - Dokument môže dostať obe.
failedpo vyčerpaní pokusov a potomdeliveredpo ručnom preposlaní. Nech vyhráva vyššieoccurredAt, nie poradie príchodu. - Poradie nie je zaručené. Opakovaná staršia udalosť môže doraziť po novšej.
Rozhoduje
occurredAta váš vlastný stav, nie poradie príchodu. - Odpovedajte rýchlo. Časový limit je 10 sekúnd. Potvrďte
2xxa prácu urobte asynchrónne; pomalý handler si vyslúži opakovania a nakoniec vypnutie. - Opakovania: 6 pokusov — 1 min, 5 min, 30 min, 2 h a 6 h po prvom. Za
neúspech sa počíta čokoľvek mimo
2xxaj chyba prenosu. - Automatické vypnutie má dva prahy, podľa toho, čo endpoint odpovedá:
20 po sebe idúcich odmietnutí (
4xxokrem408a429— „toto nikdy neprijmem"), ale až 200 pri5xx,408,429, timeoutoch a chybách prenosu („teraz nemôžem“). Pri vypnutí príde majiteľovi peňaženky e-mail s hostom a poslednou chybou; odosielanie faktúr to neovplyvní. Opätovné uloženie URL endpoint zapne. - Presmerovania sa nenasledujú. Aj
301na správne miesto je neúspech — zadajte konečnú URL. - Len HTTPS a verejne rozlíšiteľný host; privátne a loopback adresy eP24 odmietne už pri konfigurácii.
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
-
payloadmôže byťnull. Buď dokument prekročil limit pre vloženie, alebo ho eP24 nevedela načítať; dôvod je vpayloadOmittedReason. Metadáta ajdocument.urlprí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. -
Overte
payloadSha256. Prepočítajtesha256nadreceived.payloadv 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. -
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 vAdditionalDocumentReference) idú výrazne vyššie.Predvolené limity, ktoré vás zastavia skôr než eP24:
Prostredie Predvolené Pri 4 MB express.json()100 kB musíte zdvihnúť Next.js Server Actions 1 MB route handler limit nemá Django DATA_UPLOAD_MAX_MEMORY_SIZE2,5 MB musíte zdvihnúť PHP post_max_size8 MB stačí Kestrel / ASP.NET Core ~30 MB stačí Pozor na Express: 100 kB je nad 95. percentilom, ale len o desatinu — prvá faktúra s prílohou tú rezervu minie. Vzniknuté
413vaš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 textomFUNCTION_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
- Vygeneruj token v eP24 (sandbox) a ulož ho bezpečne na serveri.
- 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).
- (Voliteľné, odporúčané počas vývoja) zavolaj
POST …/documents/validatea skontrolujvalid,senderActive,receiverReachable. Oprav chyby zerrors[]. - Vygeneruj
Idempotency-Key(UUID) a ulož ho vo svojej DB spolu s ID faktúry (stav =pending). - Zavolaj
POST …/documentss hlavičkamiAuthorization,Content-Type: application/xml,Idempotency-Keya telom = XML. Timeout klienta ~60 s. - 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.
- Doručenie sleduj webhookom, nie dopytovaním. Po
202je faktúra v rukách eP24. Ak stav potrebuješ aj v ERP, zaregistruj webhook — prídeoutbox.document.delivered, prípadneoutbox.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,NSwagpre .NET,openapi-python-client).
12. Kontrolný zoznam pred spustením
- Token uložený bezpečne na serveri, volania idú z backendu (nie z prehliadača).
-
Idempotency-Keysa generuje raz na faktúru a je uložený vo vašej DB. - Ošetrené všetky stavové kódy z kap. 8.
- Opakovania pri
429/503/402používajú rovnaký kľúč a backoff. - Timeout HTTP klienta ≥ 60 s.
-
documentIdz202/200je uložené a spárované s vašou faktúrou. - Otestované v sandboxe end-to-end pred prepnutím na produkciu.
- Produkčný token má prefix
ep24api_prod_, sandboxep24api_test_. - Faktúry sa vystavujú s dnešným dátumom (inak
422 custody_refused,reason: late_issue_guard). - Ak používaš webhook: endpoint overuje podpis nad surovým telom,
deduplikuje podľa
X-EP24-Deliverya odpovedá2xxdo 10 s (8a).
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).
| Prostredie | Základná URL | Token | Vytvorenie tokenu |
|---|---|---|---|
| Sandbox | https://epodatelna24-sandbox.vercel.app | ep24api_test_ | /dashboard/wallet |
| Produkcia | https://www.epodatelna24.sk | ep24api_prod_ | /dashboard/settings |
Čo sa dá v paneli „Požiadavka“ nastaviť
- Testovací scenár — načíta vzor a nastaví, čo treba, aby odpoveď vyšla presne na daný kód.
- API token peňaženky (zobrazenie/skrytie, kontrola prefixu).
Idempotency-Key— pole je upraviteľné: vygenerujte nový kľúč, vložte vlastný, alebo ho vymažte a pošlite požiadavku bez hlavičky (400 missing_idempotency_key). Hodnota musí byť UUID, inak API odpovie rovnako.- Voliteľné hlavičky doručenia —
X-Requested-DeliveryaX-Buyer-Email. Posielajú sa len pri odoslaní, prázdne polia sa neposielajú vôbec. Pozor:emailAPI odmietne (400 email_delivery_unsupported), prijme lenpeppol. - Reset vpravo hore vráti scenár, vzor, kľúč aj hlavičky do východiskového stavu; token a prostredie ostanú.
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:
- Simulátor vždy štartuje v Sandboxe a voľbu prostredia neukladá — návrat na stránku, ktorá si potichu pamätá „Produkcia“, je presne to, ako sa omylom odošle skutočná faktúra.
- Zobrazí sa výrazný červený banner a v hlavičke červený štítok
PRODUKCIA. - Testovacie scenáre sú vypnuté — ich vzorové faktúry by sa reálne odoslali.
- „Odoslať“ je zamknuté, kým používateľ nezaškrtne potvrdenie. Jedno potvrdenie = jedno odoslanie: po odoslaní (aj po načítaní iného dokumentu) sa zaškrtnutie zruší.
- „Odoslať“ je červené a premenované na „Odoslať naozaj (produkcia)“; „Validovať“ sa stáva primárnou akciou — je nezáväzné, nepreberá zodpovednosť a nič neúčtuje.
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ár | Výsledok | Reprodukovateľnosť | Ako vzniká |
|---|---|---|---|
| Prijaté | 202 | závisí od peňaženky | platné XML + váš aktívny DIČ + dosiahnuteľný príjemca |
| Idempotentné opakovanie | 200 | závisí od peňaženky | 202, potom to isté odoslať s rovnakým kľúčom |
invalid_xml | 400 | deterministické | telo nie je well-formed XML |
missing_idempotency_key | 400 | deterministické | odoslanie bez Idempotency-Key |
email_delivery_unsupported | 400 | deterministické | X-Requested-Delivery: email — API posiela len cez Peppol |
unauthorized | 401 | deterministické | nesprávny token |
insufficient_funds | 402 | vynútené v Sandboxe | X-Ep24-Simulate: insufficient_funds |
sender_not_found | 403 | deterministické | DIČ dodávateľa nie je pod peňaženkou |
sender_not_active | 403 | vynútené v Sandboxe | X-Ep24-Simulate: sender_not_active |
idempotency_conflict | 409 | závisí od peňaženky | rovnaký kľúč, bajtovo iné telo (najprv treba 202) |
too_large | 413 | deterministické | telo sa pri odoslaní nafúkne nad limit |
validation_failed | 422 | deterministické | chýba el. adresa odberateľa (PEPPOL-EN16931-R010) |
validation_failed (súčty) | 422 | deterministické | splatná suma nesedí so súčtom (BR-CO-16) |
receiver_unreachable | 422 | v sandboxe nereprodukovateľné | sandbox nemá SMP — vyžaduje simulate trigger |
rate_limited | 429 | vynútené v Sandboxe | X-Ep24-Simulate: rate_limited (reálny limit je per-inštancia) |
upstream_unavailable | 503 | vynútené v Sandboxe | X-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čkuX-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.yamlna sandboxe a v produkcii sa líšia — sandbox dokumentujeX-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:truepre ľubovoľný identifikátor, takže dosiahnuteľnosť príjemcu sa nevyhodnocuje a422 receiver_unreachablesa v sandboxe nedá vyvolať vôbec: bezschemeIDporuší XML pravidloBR-63a validácia (ktorá beží skôr) vrátivalidation_failed; s platným identifikátorom prejde ako202. Dôsledok: sandboxové202nie 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é413s telomProblemaž po autentifikácii. Pri413preto vždy kontrolujteContent-Type, než telo parsujete ako JSON.
Štyri výsledky sa nedajú vynútiť samotnou požiadavkou — insufficient_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é meno | Doručovacia adresa |
|---|---|---|
0072656311 | Kaviareň Luna s.r.o. | demo+007265631-1@sandbox.epodatelna24.sk |
0072656312 | Stavebniny Tatra a.s. | demo+007265631-2@sandbox.epodatelna24.sk |
0072656313 | Pekáreň Zlatý klas s.r.o. | demo+007265631-3@sandbox.epodatelna24.sk |
0072656314 | IT Solutions Východ s.r.o. | demo+007265631-4@sandbox.epodatelna24.sk |
0072656315 | Autoservis 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á
- Hardcodujte len týchto šesť hodnôt. DIČ nikdy negenerujte —
companies.dicjenot null unique check (dic ~ '^\d{10}$'), takže neznáme DIČ nerozpozná žiadnu spoločnosť a kolidujúce zlyhá. - Len pod vyhradeným uid. Iná relácia (aj anonymná) dostane namespace
odvodený z
hashtext(uid)a tieto DIČ mať nebude. - Maximálne deväť klientov — DIČ je 9 číslic namespace + jednociferný index.
- Reset je idempotentný, medzi scenármi ho môžete volať voľne.
- 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, nie999999999x. 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 akoinbox.document.received.
Pozor na
0072656317–0072656319. Sú to platné indexy klientov 7–9 vo vyhradenom namespace, dnes neobsadené. Vzor presender_not_foundpreto nepoužíva žiadne z nich, ale8888888888— inak by po doseedovaní troch klientov namiesto403vrátil skutočné202.
13. Časté chyby
403 sender_not_found— DIČ odosielateľa nie je registrovaný pod vašou peňaženkou. Vzorové faktúry používajú demo firmu vyhradeného sandbox tenanta Kaviareň Luna s.r.o., DIČ0072656311; ak váš token patrí pod inú peňaženku, nahraďte odosielateľa vlastným DIČ. (Scenár „403 · sender_not_found“ používa zámerne neregistrované DIČ8888888888.)400 email_delivery_unsupported— poslali steX-Requested-Delivery: email. API doručuje výhradne cez Peppol; e-mailom pošlite faktúru z webovej aplikácie eP24.422 custody_refusedsreason: late_issue_guard— faktúra nie je vystavená dnes. Ochrana proti pokute ju do Peppolu nepustí; vystavte ju s dnešným dátumom (simulátor preto vzorom prepisuje dátumy pri načítaní).- Volanie z prehliadača zlyhá (CORS) — API je server-to-server. Volajte z backendu.
- Druhé odoslanie vytvorí duplicitu — nezabudnite posielať rovnaký
Idempotency-Keypri opakovaní tej istej faktúry. - Timeout na strane klienta — validácia + SMP + odoslanie trvajú pár sekúnd; zvýšte timeout na ~60 s.
422 receiver_unreachable— príjemca nie je registrovaný v Peppol; overte jeho identifikátor (schéma:hodnota, napr.0208:…).
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.