Qeron Billing API

REST API pro integraci billing systému Qeron do externího softwaru. Vytvoř fakturu, přijmi platbu přes Stripe nebo bankovní převod, dostávej webhooky o stavu předplatného.

Base URL: https://api.qeron.cz/api/v1
Všechny požadavky musí být přes HTTPS. API vrací JSON, přijímá JSON.
📄
POST /checkout
Vytvoř fakturu a přijmi platbu
🔍
GET /status
Zjisti stav platby nebo předplatného
🧹
POST /payment_cancel
Stornuj nezaplacenou výzvu z API
🚫
POST /subscription_cancel
Zruš předplatné zákazníka
🇪🇺
GET /vies
Ověř EU DIČ DPH v registru VIES

🔑 Autentizace

Každý požadavek vyžaduje API klíč v hlavičce. Klíče spravuj na sprava.qeron.cz/api-keys.php.

# Primární - X-Api-Key header
X-Api-Key: tvuj-api-klic

# Alternativa - Bearer token
Authorization: Bearer tvuj-api-klic
API klíče jsou v Qeronu uloženy jako SHA-256 hash - plaintext je viditelný pouze jednou při vytvoření. Webhooky se podepisují globálním HMAC secretem (společný pro všechny klíče) - zobrazíš a resetuješ ho na stránce API klíčů.

POST /api/v1/checkout

Vytvoří fakturu (proforma) pro zákazníka, odešle mu email s platebními instrukcemi a vrátí odkaz na platbu. Pro subscription_days > 0 vytvoří také Stripe Subscription Checkout s opakující se platbou.

Tělo požadavku

PoleTypPopis
productstringPOVINNÉNázev produktu nebo služby
amountnumberPOVINNÉČástka bez DPH v dané měně
emailstringPOVINNÉEmail zákazníka - faktura se pošle sem
nazevstringPOVINNÉ*Název firmy nebo jméno (* nebo ico)
vatnumbervolitelnéSazba DPH: 0, 12 nebo 21 (výchozí: 21)
currencystringvolitelnéCZK nebo EUR (výchozí: CZK)
icostringvolitelnéIČO - slouží k vyhledání existujícího zákazníka
dicstringvolitelnéDIČ
ulicestringvolitelnéFakturační adresa - ulice
mestostringvolitelnéMěsto
pscstringvolitelnéPSČ
telefonstringvolitelnéTelefon zákazníka
subscription_daysintvolitelné0 = jednorázová platba, >0 = předplatné na N dní (Stripe Subscription)
pay_methodstringvolitelnéstripe / qr / any (výchozí: any)
subscription_discountobjectvolitelnéČasově omezená Stripe sleva pro předplatné. Pouze se subscription_days > 0 a platbou kartou. Obsahuje percent_off, duration_months, volitelně code a label.
callback_urlstringvolitelnéWebhook URL - přepíše výchozí z API klíče. Musí být HTTPS na veřejné IP.
return_urlstringvolitelnéURL pro tlačítko "Zpět do aplikace" na platební stránce po zaplacení
sandboxboolvolitelnétrue = Stripe testovací klíče, false = Stripe live klíče. Výchozí: false
statstringvolitelnéNázev státu odběratele (např. "Slovensko", "Německo"). Výchozí: "Česká republika".
reverse_chargeboolvolitelnéPřenesení daňové povinnosti pro EU B2B (Reverse Charge). Vyžaduje "vat": 0. Na faktuře se zobrazí RC doložka. Výchozí: false.
cURL
PHP
Python
curl -X POST https://api.qeron.cz/api/v1/checkout \
  -H "X-Api-Key: tvuj-klic" \
  -H "Content-Type: application/json" \
  -d '{
    "product":           "Tarif Pro",
    "amount":            818.18,
    "vat":               21,
    "currency":          "CZK",
    "email":             "zakaznik@firma.cz",
    "nazev":             "Firma s.r.o.",
    "ico":               "12345678",
    "subscription_days": 30,
    "pay_method":        "stripe",
    "subscription_discount": {
      "percent_off":     50,
      "duration_months": 3,
      "code":            "WEBZAP50",
      "label":           "WebZap 50 %"
    },
    "callback_url":      "https://tvujweb.cz/api/billing-webhook",
    "return_url":        "https://tvujweb.cz/dashboard?activated=1",
    "sandbox":           false
  }'
$payload = [
    'product'           => 'Tarif Pro',
    'amount'            => 818.18,
    'vat'               => 21,
    'currency'          => 'CZK',
    'email'             => 'zakaznik@firma.cz',
    'nazev'             => 'Firma s.r.o.',
    'ico'               => '12345678',
    'subscription_days' => 30,
    'pay_method'        => 'stripe',
    'subscription_discount' => [
        'percent_off'     => 50,
        'duration_months' => 3,
        'code'            => 'WEBZAP50',
        'label'           => 'WebZap 50 %',
    ],
    'callback_url'      => 'https://tvujweb.cz/api/billing-webhook',
    'return_url'        => 'https://tvujweb.cz/dashboard?activated=1',
    'sandbox'           => false,
];

$resp = file_get_contents('https://api.qeron.cz/api/v1/checkout', false,
    stream_context_create(['http' => [
        'method'  => 'POST',
        'header'  => "Content-Type: application/json\r\nX-Api-Key: {$apiKey}\r\n",
        'content' => json_encode($payload),
    ]])
);
$data = json_decode($resp, true);

// Pro předplatné přesměruj na Stripe Checkout
header('Location: ' . ($data['stripe_url'] ?? $data['payment_url']));
exit;
import requests

resp = requests.post(
    "https://api.qeron.cz/api/v1/checkout",
    headers={"X-Api-Key": api_key},
    json={
        "product":           "Tarif Pro",
        "amount":            818.18,
        "vat":               21,
        "currency":          "CZK",
        "email":             "zakaznik@firma.cz",
        "nazev":             "Firma s.r.o.",
        "ico":               "12345678",
        "subscription_days": 30,
        "pay_method":        "stripe",
        "subscription_discount": {
            "percent_off":     50,
            "duration_months": 3,
            "code":            "WEBZAP50",
            "label":           "WebZap 50 %",
        },
        "callback_url":      "https://tvujweb.cz/api/billing-webhook",
        "return_url":        "https://tvujweb.cz/dashboard?activated=1",
        "sandbox":           False,
    }
)
resp.raise_for_status()
data = resp.json()

# Pro předplatné přesměruj na Stripe Checkout
redirect_url = data.get("stripe_url") or data["payment_url"]

Odpověď 200

200 OK
application/json
{
  "payment_url": "https://platba.qeron.cz/faktura.php?token=abc123def456...",
  "stripe_url":  "https://checkout.stripe.com/c/pay/cs_live_...",
  "invoice_id":  42,
  "vs":          "1704067200",
  "token":       "abc123def456ghi789...",
  "is_sandbox":  false,
  "stripe_mode": "live",
  "subscription_discount": {
    "percent_off":     50,
    "duration_months": 3,
    "code":            "WEBZAP50",
    "label":           "WebZap 50 %"
  }
}
payment_urlPlatební stránka na platba.qeron.cz - zákazník vidí fakturu, QR kód a Stripe tlačítko
stripe_urlPřímý Stripe Checkout - pro subscription_days > 0 je to Subscription mode (opakující se platba). Je null pokud Stripe není nakonfigurován nebo pay_method=qr
subscription_discountVrací použitou časově omezenou slevu pro Stripe předplatné, nebo null. amount v požadavku má zůstat plná běžná cena; Stripe odečte slevu jen po zadaný počet měsíců a potom účtuje plnou cenu.
invoice_idID proformy - použij pro GET /status dotaz na stav
vsVariabilní symbol (Unix timestamp) - pro párování FIO platby
tokenVeřejný token faktury
💡
Doporučení pro předplatné: vždy přesměruj na stripe_url pokud je k dispozici - zákazník tam zadá kartu a souhlasí s opakujícím se strháváním. payment_url je záloha pro případ QR platby nebo když Stripe není nakonfigurován. QR kód používá jako zprávu pro příjemce první položku faktury, aby zákazník i účetnictví viděli, za co se platí.
Sandbox (testovací Stripe): při "sandbox": true se použijí testovací Stripe klíče (sk_test_ / pk_test_), při "sandbox": false se použijí live klíče (sk_live_ / pk_live_) bez ohledu na globální přepínač adminu. stripe_url povede na testovací Stripe Checkout - platit lze pouze testovacími kartami. Webhooky z testovacích sessions mají vlastní Stripe podpis - ujisti se, že tvůj server zpracovává testovací webhooky správně.

GET /api/v1/status

Zjistí aktuální stav faktury nebo předplatného. Použij jako zálohu k webhookům - denní polling zachytí zameškaná volání.

Query parametry (jeden z)

ParametrPopis
?invoice_id=42Interní ID faktury (z odpovědi /checkout)
?vs=1704067200Variabilní symbol
?token=abc123Veřejný token faktury
curl -H "X-Api-Key: tvuj-klic" \
  "https://api.qeron.cz/api/v1/status?invoice_id=42"

Odpověď 200

200 OK
application/json
{
  "invoice_id":  42,
  "vs":          "1704067200",
  "cislo":       "261100001",
  "je_proforma": false,
  "stav":        "paid",
  "paid":        true,
  "paid_at":     "2026-04-30 10:30:00",
  "amount":      1197.90,
  "currency":    "CZK",
  "is_sandbox":  false,
  "customer": {
    "email": "zakaznik@firma.cz",
    "ico":   "12345678",
    "nazev": "Firma s.r.o."
  },
  "subscription": {
    "stav":      "active",
    "obdobi_od": "2026-04-30",
    "obdobi_do": "2026-05-30"
  }
}

subscription je null pro jednorázové platby nebo dokud proforma nebyla zaplacena.
is_sandbox: true pokud platba běží přes Stripe testovací klíče.

POST /api/v1/payment_cancel

Stornuje nezaplacenou platební výzvu vytvořenou přes API. Výzva se nemaže, pouze se přepne do stavu cancelled. Pokud má otevřenou Stripe Checkout Session nebo PaymentIntent, Qeron se ji pokusí zneplatnit. Pokud měla výzva callback_url, Qeron pošle webhook payment.cancelled.

Přes API lze stornovat pouze nezaplacenou proformu vytvořenou stejným API klíčem. Zaplacenou fakturu nebo ostrý daňový doklad endpoint nezruší.

Tělo požadavku

PoleTypPopis
invoice_idintPOVINNÉ*ID platební výzvy z odpovědi /checkout
vsstringPOVINNÉ*Variabilní symbol - alternativa k invoice_id
tokenstringPOVINNÉ*Veřejný token faktury - alternativa k invoice_id
reasonstringvolitelnéDůvod storna pro audit a webhook, výchozí api_cancel

* Stačí jeden identifikátor: invoice_id, vs nebo token.

curl -X POST https://api.qeron.cz/api/v1/payment_cancel \
  -H "X-Api-Key: tvuj-klic" \
  -H "Content-Type: application/json" \
  -d '{
    "invoice_id": 42,
    "reason": "customer_abandoned_checkout"
  }'

Odpověď 200

200 OK
application/json
{
  "ok":           true,
  "invoice_id":   42,
  "vs":           "1704067200",
  "stav":         "cancelled",
  "stripe_error": null
}

Chybové stavy: 404 pokud výzva neexistuje, 403 pokud nepatří API klíči, 409 pokud už je zaplacená nebo nejde o platební výzvu.

POST /api/v1/subscription_cancel

Zruší předplatné zákazníka. Výchozí chování je zrušení na konci aktuálního fakturačního období - zákazník má přístup do konce zaplacené periody, žádné vracení peněz. Pro okamžité zrušení (porušení podmínek, refund) použij "immediate": true.

API může rušit pouze předplatná vytvořená přes stejný API klíč. Předplatná vytvořená ručně v adminu přes API zrušit nelze.

Tělo požadavku

PoleTypPopis
subscription_idintPOVINNÉ*Interní Qeron ID předplatného
stripe_subscription_idstringPOVINNÉ*Stripe sub_... ID - alternativa k subscription_id
immediateboolvolitelnéfalse = zruš na konci periody (výchozí), true = zruš okamžitě

* Stačí jedno z obou.

Na konci periody
Okamžité zrušení
curl -X POST https://api.qeron.cz/api/v1/subscription_cancel \
  -H "X-Api-Key: tvuj-klic" \
  -H "Content-Type: application/json" \
  -d '{"subscription_id": 42}'
curl -X POST https://api.qeron.cz/api/v1/subscription_cancel \
  -H "X-Api-Key: tvuj-klic" \
  -H "Content-Type: application/json" \
  -d '{"subscription_id": 42, "immediate": true}'

Odpověď 200

200 OK
{
  "ok":              true,
  "subscription_id": 42,
  "stav":            "cancel_scheduled",
  "obdobi_do":       "2026-05-30",
  "immediate":       false,
  "stripe_error":    null
}

stav: "cancel_scheduled" = zruší se na konci periody, Stripe doručí customer.subscription.deleted webhook — Qeron následně pošle subscription.cancelled na tvůj callback.
stav: "cancelled" = okamžité zrušení, webhook dorazí do několika sekund.

Pokud se zrušení Stripe subscription nepodaří (výpadek, neplatný klíč), endpoint vrátí 502 s stripe_error a předplatné zůstává aktivní - lokálně se nikdy neruší něco, co Stripe dál účtuje. Požadavek opakuj později.

GET /api/v1/vies

Ověří DIČ DPH v evropském registru VIES (VAT Information Exchange System). Vrátí název a adresu firmy tak, jak jsou zapsány v registru. Použij před vystavením faktury s Reverse Charge pro ověření, že odběratel je platný plátce DPH v EU.

Formát DIČ: první dvě písmena jsou kód státu EU, zbytek je číslo plátce. Příklady: CZ04694074, SK2022489522, DE123456789. Řecko používá kód EL (ne GR).

Query parametry

ParametrPopis
?dic=SK2022489522POVINNÉDIČ DPH ke kontrole (formát: kód státu + číslo plátce)
cURL
PHP
Python
curl -H "X-Api-Key: tvuj-klic" \
  "https://api.qeron.cz/api/v1/vies?dic=SK2022489522"
$dic = 'SK2022489522';
$resp = file_get_contents(
    "https://api.qeron.cz/api/v1/vies?dic={$dic}",
    false,
    stream_context_create(['http' => [
        'header' => "X-Api-Key: {$apiKey}\r\n",
    ]])
);
$data = json_decode($resp, true);

if ($data['valid']) {
    // Odběratel je platný plátce DPH - lze vystavit fakturu s RC
    $nazev   = $data['name'];
    $adresa  = $data['address'];
}
import requests

resp = requests.get(
    "https://api.qeron.cz/api/v1/vies",
    headers={"X-Api-Key": api_key},
    params={"dic": "SK2022489522"},
)
data = resp.json()

if data["valid"]:
    # Odběratel je platný plátce DPH - lze vystavit fakturu s RC
    nazev  = data["name"]
    adresa = data["address"]

Odpověď 200 - platné DIČ

200 OK
application/json
{
  "valid":        true,
  "dic":          "SK2022489522",
  "country_code": "SK",
  "vat_number":   "2022489522",
  "name":         "Firma Slovakia s.r.o.",
  "address":      "Obchodná 1, 811 06 Bratislava",
  "error":        null
}

Odpověď 200 - neplatné nebo nenalezené DIČ

200 OK
application/json
{
  "valid":        false,
  "dic":          "SK9999999999",
  "country_code": "SK",
  "vat_number":   "9999999999",
  "name":         null,
  "address":      null,
  "error":        "INVALID_INPUT"
}

Endpoint vrací vždy 200 - pole valid určuje výsledek ověření. Chybové HTTP kódy (401, 422, 429) signalizují problém s požadavkem, ne s DIČ. error obsahuje kód z VIES API (INVALID_INPUT, SERVICE_UNAVAILABLE apod.) nebo null při úspěchu.

VIES je provozován Evropskou komisí a dostupnost není garantována 24/7. Při SERVICE_UNAVAILABLE nebo MS_UNAVAILABLE zkus požadavek opakovat za několik minut. Endpoint sdílí rate limit s /checkout (100 req/hod na API klíč).

Propojení s Reverse Charge

Typický flow pro EU B2B fakturu s přenesením daňové povinnosti:

1. GET /vies?dic=DE123456789 → valid: true
2. POST /checkout s "vat": 0, "reverse_charge": true, "stat": "Německo", "dic": "DE123456789"
3. Qeron vystaví fakturu s 0% DPH a doložkou RC
4. Zákazník odvede DPH sám ve své zemi dle čl. 196 Směrnice 2006/112/ES

🪝 Webhooky

Qeron automaticky odesílá POST na callback_url při klíčových událostech. Callback URL musí být veřejná HTTPS adresa.

Ověření podpisu

Každý webhook obsahuje hlavičku X-Qeron-Signature s HMAC-SHA256 podpisem těla. Klíč (HMAC secret) najdeš na stránce API klíčů v admin panelu.

PHP
Python
$payload  = file_get_contents('php://input');
$podpis   = $_SERVER['HTTP_X_QERON_SIGNATURE'] ?? '';
$ocekavan = hash_hmac('sha256', $payload, QERON_HMAC_SECRET);

if (!hash_equals($ocekavan, $podpis)) {
    http_response_code(403);
    exit('Neplatný podpis');
}

$event = json_decode($payload, true);

switch ($event['event']) {
    case 'invoice.paid':
    case 'subscription.renewed':
        aktivujPristup($event['customer']['email']);
        ulozDatum($event['customer']['email'], $event['subscription_until']);
        break;
    case 'payment.cancelled':
        zrusCekajiciObjednavku($event['invoice_id']);
        break;
    case 'subscription.cancelled':
    case 'subscription.payment_failed':
        vypniPristup($event['customer']['email']);
        break;
}
import hmac, hashlib
from flask import request, abort

HMAC_SECRET = "tvuj-hmac-secret"

def verify(payload: bytes, signature: str) -> bool:
    expected = hmac.new(HMAC_SECRET.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route('/api/billing-webhook', methods=['POST'])
def webhook():
    sig = request.headers.get('X-Qeron-Signature', '')
    if not verify(request.data, sig):
        abort(403)

    event = request.get_json()
    ev = event['event']

    if ev in ('invoice.paid', 'subscription.renewed'):
        activate_access(event['customer']['email'], event['subscription_until'])
    elif ev == 'payment.cancelled':
        cancel_pending_order(event['invoice_id'])
    elif ev in ('subscription.cancelled', 'subscription.payment_failed'):
        revoke_access(event['customer']['email'])

    return '', 200

📩 invoice.paid

Zákazník zaplatil fakturu (Stripe Checkout nebo FIO bankovní převod). Pro předplatné (subscription_days > 0) obsahuje subscription_until.

invoice.paid
{
  "event":              "invoice.paid",
  "invoice_id":         43,
  "proforma_id":        42,
  "vs":                 "1704067200",
  "cislo":              "261100001",
  "amount":             1197.90,
  "currency":           "CZK",
  "customer": {
    "email": "zakaznik@firma.cz",
    "ico":   "12345678",
    "nazev": "Firma s.r.o."
  },
  "paid_at":            "2026-04-30 10:30:00",
  "subscription_until": "2026-05-30"
}

subscription_until je vyplněno pouze pro subscription_days > 0.

🧹 payment.cancelled

Nezaplacená platební výzva byla stornována přes POST /payment_cancel. Použij pro vyčištění čekající objednávky v napojeném systému.

payment.cancelled
{
  "event":      "payment.cancelled",
  "reason":     "api_cancel",
  "invoice_id": 42,
  "vs":         "1704067200",
  "cislo":      "261000042",
  "stav":       "cancelled",
  "paid":       false,
  "amount":     1197.90,
  "currency":   "CZK",
  "is_sandbox": false,
  "customer": {
    "email": "zakaznik@firma.cz",
    "ico":   "12345678",
    "nazev": "Firma s.r.o."
  }
}

🔄 subscription.renewed

Stripe automaticky strhnul obnovu předplatného. Zákazník nic nemusel dělat.

subscription.renewed
{
  "event":              "subscription.renewed",
  "invoice_id":         55,
  "amount":             1197.90,
  "currency":           "CZK",
  "customer": {
    "email": "zakaznik@firma.cz",
    "ico":   "12345678",
    "nazev": "Firma s.r.o."
  },
  "subscription_until": "2026-06-30"
}

❌ subscription.cancelled

Předplatné bylo zrušeno. Může přijít ihned (okamžité zrušení) nebo na konci zaplacené periody.

subscription.cancelled
{
  "event":  "subscription.cancelled",
  "reason": "user_cancel",
  "customer": {
    "email": "zakaznik@firma.cz",
    "nazev": "Firma s.r.o."
  }
}

reason: "user_cancel" - zákazník nebo admin zrušil  |  "api_cancel" - zrušeno přes API  |  "payment_failed" - všechny Stripe retry selhaly  |  "payment_overdue" - obnova bankovním převodem nebyla zaplacena do splatnosti

💳 subscription.payment_failed

Stripe se nepodařilo strhnout obnovu (karta bez pokrytí, expirovaná atd.). Stripe se pokusí automaticky znovu. Teprve pokud všechny pokusy selžou, přijde subscription.cancelled.

subscription.payment_failed
{
  "event":  "subscription.payment_failed",
  "customer": {
    "email": "zakaznik@firma.cz",
    "nazev": "Firma s.r.o."
  }
}

🔁 Tok předplatného (Stripe)

1. POST /api/v1/checkout { subscription_days: 30, pay_method: "stripe" } odpověď: stripe_url + payment_url + invoice_id 2. Přesměruj na stripe_url zákazník zadá kartu, souhlasí s opakováním 3. Stripe zpracuje platbu webhook checkout.session.completed Qeron: aktivuje předplatné, odešle fakturu emailem Qeron → tvůj callback: "invoice.paid" + subscription_until Zákazník přesměrován na success page (return_url) 4. Den před obnovením zákazník dostane email + cancel link 5a. Zákazník NIC NEDĚLÁ Stripe strhne automaticky Qeron → tvůj callback: "subscription.renewed" + nový subscription_until 5b. Zákazník ZRUŠÍ cancel link / API / admin Stripe: cancel_at_period_end = true (ne okamžité) zákazník má přístup do konce periody Na konci: Qeron → tvůj callback: "subscription.cancelled" 5c. Platba selže Qeron → tvůj callback: "subscription.payment_failed" Stripe zkusí znovu (smart retries) Všechny pokusy selžou: "subscription.cancelled"
🛡
Doporučená strategie spolehlivosti: webhooky jako primární (okamžitá reakce) + denní polling přes GET /status jako záloha pro případ výpadku callbacku.

⚡ HTTP stavové kódy

Chybové odpovědi mají vždy tvar: { "error": "popis chyby v češtině" }

200OK - úspěch
400Špatný JSON v těle požadavku
401Chybějící nebo neplatný API klíč
403Faktura nebo předplatné nepatří tomuto API klíči
404Faktura nebo předplatné nenalezeno
405Špatná HTTP metoda
409Konflikt stavu (např. předplatné je již zrušeno)
422Chybějící nebo neplatná pole v požadavku
429Rate limit - překročeno 100 checkout požadavků za hodinu na API klíč
502Stripe operace selhala (zrušení předplatného, vytvoření slevy) - stav se nezměnil, opakuj později