CPDS API integrácia — sprievodca pre vývojárov

Tento článok je technický sprievodca pre vývojárov, ktorí integrujú slovenský účtovný systém, ERP alebo SaaS aplikáciu s CPDS poskytovateľom pre povinnú elektronickú fakturáciu od 1.1.2027. Pokrýva REST API, autentizáciu, validáciu UBL XML, webhooks, sandbox testovanie a security model.

Pre koho je článok: vývojári, integrační inžinieri, IT architekti slovenských firiem alebo ich dodávateľov. Predpokladá znalosť HTTP/REST, JSON, XML a základov OAuth 2.0.

Architektonický prehľad

Z pohľadu integrácie má Peppol e-fakturácia 4 vrstvy, s ktorými sa stretnete:

  1. Vaša aplikácia — generuje obchodné dáta (faktúry).
  2. CPDS REST API — rozhranie medzi vašou aplikáciou a Peppol Access Pointom (vaším CPDS poskytovateľom).
  3. AS4 protokol — komunikácia medzi CPDS-CPDS, riešená poskytovateľom, neviditeľná pre vás.
  4. SML/SMP — globálna service discovery vrstva pre Peppol.

Vy implementujete len vrstvu 1 a komunikáciu na vrstvu 2. Vrstvy 3 a 4 sú zodpovednosťou CPDS poskytovateľa.

Pre detailný architektonický rozbor pozri Pre IT oddelenia a Peppol BIS 3.0 vs UBL 2.1 — vysvetlenie.

Voľba CPDS poskytovateľa pre integráciu

Pri výbere CPDS pre integráciu zhodnoťte:

Slovenskí CPDS poskytovatelia ako ePošťák API ponúkajú REST API s OpenAPI 3.1 dokumentáciou, sandbox prostredím, SDK v 6 jazykoch a Peppol Directory lookup nástrojom pre overenie účastníkov pred odoslaním. Pri ostatných poskytovateľoch sa pýtajte explicitne.

Autentizácia — OAuth 2.0 vs API kľúče

Štandardné CPDS API podporujú dva mechanizmy:

OAuth 2.0 Client Credentials Flow

Najbezpečnejší pre produkciu. Postup:

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=invoices:read invoices:write

Response:

{
  "access_token": "eyJhbGc...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Token použijete v hlavičke Authorization: Bearer eyJhbGc... pre všetky ďalšie volania. Po expirácii (typicky 1 hodina) získate nový.

Best practices:

API kľúče

Jednoduchšie, ale menej bezpečné. Vhodné pre interné servisy a sandbox testovanie.

GET /v1/invoices
Authorization: Bearer YOUR_API_KEY

Best practices:

Príjem dokumentov — webhook pattern

Recommended pattern pre prijímanie e-faktúr je webhook, nie polling. CPDS pošle HTTP POST na váš endpoint pri každej príchodzej faktúre.

Registrácia webhook

POST /v1/webhooks
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "url": "https://your-app.com/webhooks/cpds",
  "events": ["invoice.received", "invoice.delivered", "invoice.rejected"],
  "secret": "webhook_signing_secret_min_32_chars"
}

Spracovanie webhook

CPDS pošle:

POST /webhooks/cpds HTTP/1.1
Host: your-app.com
Content-Type: application/json
X-CPDS-Signature: sha256=abc123...
X-CPDS-Event: invoice.received
X-CPDS-Delivery-Id: deliv_xyz

{
  "event": "invoice.received",
  "invoice_id": "inv_abc123",
  "received_at": "2026-05-09T10:30:00Z",
  "sender": {
    "participant_id": "0245:SK1234567890",
    "name": "Acme s.r.o."
  },
  "metadata": {
    "amount": 1234.56,
    "currency": "EUR",
    "due_date": "2026-06-08"
  }
}

Validácia HMAC podpisu

Vždy validujte signature pred spracovaním. Pseudokód v Node.js:

import crypto from 'crypto';

function validateSignature(rawBody, signatureHeader, secret) {
  const [algo, hash] = signatureHeader.split('=');
  const expected = crypto
    .createHmac(algo, secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(hash, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

Pozor: používa sa HMAC SHA-256 alebo SHA-512 podľa konkrétneho CPDS. Niektorí poskytovatelia migrovali z SHA-256 na SHA-512 — overte aktuálny štandard v dokumentácii.

Idempotenica a retry

Webhook môže byť doručený viackrát (CPDS retry-uje pri 5xx alebo timeoutoch). Vaš handler musí byť idempotentný:

async function handleWebhook(payload) {
  const deliveryId = payload.headers['x-cpds-delivery-id'];

  // Check if already processed (DB unique constraint or Redis SET NX)
  const isDuplicate = await db.webhookDeliveries.exists(deliveryId);
  if (isDuplicate) return { status: 'already_processed' };

  // Process
  await db.transaction(async (tx) => {
    await tx.webhookDeliveries.insert({ id: deliveryId, processed_at: new Date() });
    await tx.invoices.upsert({ id: payload.body.invoice_id, ... });
  });

  return { status: 'ok' };
}

Rýchla odpoveď

Vráťte HTTP 2xx do 10 sekúnd. Inak CPDS retry-uje. Najjednoduchšie:

  1. Webhook handler validuje signature a enqueueuje úlohu (BullMQ, Sidekiq, AWS SQS).
  2. Vráti 200 hneď.
  3. Background worker spracuje úlohu mimo HTTP cyklu.

Sťahovanie obsahu faktúry

Webhook obsahuje len ID a metadata. Pre samotný UBL XML zavolajte:

GET /v1/invoices/{id}
Authorization: Bearer YOUR_TOKEN
Accept: application/xml

Response:

<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
         xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
         xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
  <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
  <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
  <cbc:ID>2026-001</cbc:ID>
  <cbc:IssueDate>2026-05-09</cbc:IssueDate>
  <!-- ... rest of invoice ... -->
</Invoice>

Pre PDF (ak ho CPDS generuje) typicky Accept: application/pdf.

Odosielanie dokumentov

Validácia pred odoslaním

Vždy zavolajte dry-run validation pred ostrým submitom. Šetrí to zbytočné rejected faktúry:

POST /v1/invoices/validate
Authorization: Bearer YOUR_TOKEN
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<Invoice ...>...</Invoice>

Response (úspech):

{ "valid": true, "warnings": [] }

Response (chyba):

{
  "valid": false,
  "errors": [
    {
      "rule": "BR-S-08",
      "level": "fatal",
      "location": "/Invoice/cac:InvoiceLine[1]/cac:Item/cac:ClassifiedTaxCategory",
      "message": "Invoice line VAT category code is required"
    }
  ]
}

Submit

POST /v1/invoices
Authorization: Bearer YOUR_TOKEN
Content-Type: application/xml
Idempotency-Key: invoice-2026-001-v1

<?xml version="1.0" encoding="UTF-8"?>
<Invoice ...>...</Invoice>

Response:

{
  "id": "inv_xyz789",
  "status": "submitted",
  "tracking_id": "trk_abc123",
  "submitted_at": "2026-05-09T10:35:00Z"
}

Sledovanie stavu

Status sa mení asynchrónne (delivered → acknowledged alebo delivered → rejected). Sledovať môžete cez:

Generovanie UBL XML

Generovanie UBL XML zo svojich obchodných dát zvládnete buď cez knižnicu, alebo manuálne (pri jednoduchých faktúrach).

Odporúčané knižnice

Mapovanie polí

Najčastejšie polia, ktoré potrebujete vyplniť:

UBL poleVýznamPríklad
cbc:IDČíslo faktúry”2026-001”
cbc:IssueDateDátum vystavenia”2026-05-09”
cbc:DueDateDátum splatnosti”2026-06-08”
cbc:DocumentCurrencyCodeMena”EUR”
cac:AccountingSupplierPartyPredajcacelý blok s IČO/DIČ/adresou
cac:AccountingCustomerPartyKupujúcicelý blok
cac:InvoiceLinePoložky faktúryjeden blok per položka
cac:TaxTotalDPH summarysadzby a celková DPH
cac:LegalMonetaryTotalSúčtynetto, DPH, brutto

Pre slovenské špecifiká (CIUS) povinné polia:

Sandbox testovanie

Setup

  1. Zaregistrujte sandbox účet u CPDS (typicky zadarmo).
  2. Získajte sandbox endpoint (typicky https://sandbox.cpds.example.com) a sandbox API kľúče.
  3. Získajte test Peppol participant ID v slovenskom formáte 0245:DIČ (napr. 0245:SKTEST123 v sandboxe).

Test plan

Minimálny set testov pre integráciu:

Test pre slovenský trh

Pre testovanie slovenských špecifík odporúčame:

Production deployment checklist

Pred prepnutím na produkciu:

Code sample — kompletný flow v Node.js

Zjednodušený end-to-end flow pre prijímanie a odosielanie:

import express from 'express';
import crypto from 'crypto';
import { Queue } from 'bullmq';

const app = express();
const queue = new Queue('cpds-invoices');

const CPDS_API = 'https://api.your-cpds.com';
const CPDS_TOKEN = process.env.CPDS_TOKEN;
const WEBHOOK_SECRET = process.env.CPDS_WEBHOOK_SECRET;

// Webhook handler
app.post('/webhooks/cpds', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.header('X-CPDS-Signature');
  const event = req.header('X-CPDS-Event');
  const deliveryId = req.header('X-CPDS-Delivery-Id');

  // 1. Validate signature
  const expected = crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex');
  const provided = signature.replace('sha256=', '');
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided))) {
    return res.status(401).send('invalid signature');
  }

  // 2. Enqueue async processing
  await queue.add('process-event', {
    event,
    deliveryId,
    payload: JSON.parse(req.body.toString()),
  }, {
    jobId: deliveryId, // idempotency
    attempts: 5,
    backoff: { type: 'exponential', delay: 1000 },
  });

  // 3. Acknowledge fast
  res.status(200).send('ok');
});

// Send invoice
async function sendInvoice(ublXml) {
  // Validate first
  const validation = await fetch(`${CPDS_API}/v1/invoices/validate`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${CPDS_TOKEN}`,
      'Content-Type': 'application/xml',
    },
    body: ublXml,
  });
  const validationResult = await validation.json();
  if (!validationResult.valid) {
    throw new Error(`Validation failed: ${JSON.stringify(validationResult.errors)}`);
  }

  // Submit
  const idempotencyKey = `invoice-${extractInvoiceId(ublXml)}`;
  const submit = await fetch(`${CPDS_API}/v1/invoices`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${CPDS_TOKEN}`,
      'Content-Type': 'application/xml',
      'Idempotency-Key': idempotencyKey,
    },
    body: ublXml,
  });
  return await submit.json();
}

app.listen(3000);

Pre ďalšie čítanie

Externé zdroje a citácie