Preskočiť na obsah
API dokumentácia

Objednávky a udalosti cez API a webhook

Táto stránka je pre e-shop na akejkoľvek platforme, ktorý Poštárna nenapojí sama (feed, import, jeden riadok kódu). Objednávky, zákazníkov a udalosti pošlete cez REST API v1; Poštárna z nich spúšťa automatizácie po nákupe, počíta tržby z e-mailov a plní segmenty.

Potrebujete: API kľúč s rolou marketing_manager (oblasti contacts a campaigns), ID firmy ({tenant}) a ID e-shopu (shopId), postup v Začíname. Na stiahnutie: kolekcia pre Postman so všetkými požiadavkami z tejto stránky (vyplníte premenné apiKey, tenant, shopId).

Tri cesty, jedna pravda

ČoCestaKedy
ObjednávkyPOST /orderspo vytvorení objednávky a po každej zmene stavu
Zákaznícivzniknú z objednávkykupujúci bez súhlasu je zákazník bez marketingového súhlasu (customer), e-maily nedostáva
OdberateliaPOST /contacts so súhlasomlen keď človek súhlas naozaj dal (formulár, pokladňa)
UdalostiPOST /contact-eventskošík, návšteva, vlastná udalosť (napríklad registrácia do vernostného programu)
Z prehliadačajeden riadok postarna.jszobrazenie produktu, košík, pokladňa, objednávka; popis v aplikácii a v integráciách

Objednávky

Objednávka sa ukladá podľa dvojice (shopId, id), kde id je číslo objednávky vo vašom e-shope. To je kľúč idempotencie: tá istá objednávka poslaná znova vráti 200 a "change": "unchanged" a nič nezmení; nová vráti 201 created, zmenená 200 updated. Opakovať požiadavku po výpadku siete je preto vždy bezpečné.

Terminál
curl -sS -X POST "https://www.postarna.sk/api/v1/tenants/$TENANT/orders" \
  -H "Authorization: Bearer $POSTARNA_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @- <<'JSON'
{
  "id": "2026001234",
  "shopId": "2",
  "createdAt": "2026-10-01T09:30:00+02:00",
  "email": "jana.novakova@example.sk",
  "status": "Vybavuje sa",
  "currency": "EUR",
  "totalWithVat": "52.90",
  "totalWithoutVat": "43.00",
  "items": [
    {"id": "KAVA-1KG", "name": "Káva Kolumbia 1 kg", "quantity": 1,
     "price": {"withVat": "52.90", "withoutVat": "43.00", "currency": "EUR"},
     "attributes": {"Category": "Káva", "Brand": "Example"}}
  ]
}
JSON
  • Každý zápis nahrádza celú objednávku. Pri zmene stavu pošlite celú objednávku znova s novým status (a paidAt, keď je zaplatená).
  • status je názov stavu vo vašom e-shope („processing“, „Zaplatená“ ...). Na kanonický stav ho prevedie mapa stavov; názov bez pravidla je other a nikdy sa nepočíta ako nákup.
  • currency smie byť len mena e-shopu (e-shop v EUR neprijme CZK). Sumy sú desatinné reťazce v mene objednávky; Poštárna ich nikdy neprevádza ani nesčítava naprieč menami.
  • Časy ISO 8601 so zónou (2026-10-01T09:30:00+02:00 alebo ...Z). Čas bez zóny je chyba 400.
  • Zmazaná objednávka: "deleted": true (beh po nákupe sa ukončí).
  • Kupujúci sa spáruje s kontaktom podľa e-mailu; bez kontaktu vznikne zákazník bez marketingového súhlasu.
  • Limity: 2 500 požiadaviek nárazovo, dopĺňa sa 4 za sekundu, 100 000 za deň na firmu (hlavičky X-RateLimit-*, po vyčerpaní 429 s Retry-After).

Mapovanie polí

PoštárnaWooCommerce (REST, webhook)Shopify (webhook orders/*)Poznámka
idididstabilné a jedinečné v e-shope
eshopCodenumbername (#1001)číslo, ktoré vidí zákazník
createdAtdate_created_gmt + Zcreated_atso zónou
paidAtdate_paid_gmt + Zpodľa financial_status
statusstatusfinancial_status, cancelled_atnázov stavu, mapa stavov ho prevedie
emailbilling.emailemail
currencycurrencycurrencymena e-shopu
totalWithVattotaltotal_price
totalWithoutVattotal mínus total_taxtotal_price mínus total_tax
items[].idline_items[].sku (inak product_id)line_items[].sku (inak variant_id)ten istý kód ako v produktovom feede
items[].name, quantityline_items[].name, quantityline_items[].title, quantity
items[].price.withoutVatline_items[].pricejednotková cena
items[].price.withVatline_items[].price (podľa nastavenia daní obchodu)jednotková cena
promoCodesUsedcoupon_lines[].codediscount_codes[].code

Názvy polí platforiem uvádzame podľa ich verejnej dokumentácie; na živom obchode ich overte skôr, ako napojenie zapnete.

Odporúčaná mapa stavov WooCommerce (PUT /order-states): pending a on-hold = unpaid, processing = paid, completed = delivered, cancelled a failed = cancelled, refunded = returned.

Zákazníci a odberatelia

  • Zákazník nepotrebuje vlastné volanie: vznikne z prvej objednávky s jeho e-mailom ako kontakt v stave customer (bez marketingového súhlasu). Automatizácie po nákupe a zákaznícka výnimka (soft opt-in) s ním pracujú podľa nastavenia e-shopu.
  • Odberateľ so súhlasom: POST /contacts s účelom spracovania a potvrdením, že súhlas máte doložený. Bez súhlasu kontakt cez API nezakladajte.
Terminál
curl -sS -X POST "https://www.postarna.sk/api/v1/tenants/$TENANT/contacts" \
  -H "Authorization: Bearer $POSTARNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "peter.horvath@example.sk", "shopId": "2", "firstName": "Peter", "purposeId": "1", "consentConfirmed": true}'
  • Existujúca adresa vráti 409 conflict; úpravy cez PATCH /contacts/{id}. Odhlásený alebo potlačený kontakt sa cez API nikdy znova neprihlási. Podrobnosti v Kontakty a súhlasy.

Udalosti

POST /contact-events zapíše udalosť existujúceho kontaktu (podľa email alebo contactId); kontakt nikdy nezaloží. Spúšťače automatizácií na ňu reagujú hneď.

typeTeloNa čo
cartshopId, items (code, name, quantity, price, url), currency, totalcelý obsah košíka, prázdny = vyprázdnený; Opustený košík
visitshopId, url, visitType (product, category, cart, order_done ...)návšteva stránky
customname (malé písmená, číslice, pomlčky), data (objekt)vlastná udalosť pre spúšťač Vlastná udalosť
Terminál
curl -sS -X POST "https://www.postarna.sk/api/v1/tenants/$TENANT/contact-events" \
  -H "Authorization: Bearer $POSTARNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "cart", "email": "jana.novakova@example.sk", "shopId": "2", "currency": "EUR", "total": "52.90", "items": [{"code": "KAVA-1KG", "name": "Káva Kolumbia 1 kg", "quantity": 1, "price": "52.90"}]}'
  • Odpoveď 201 {"id": "..."}, neznámy kontakt 404, najviac 60 udalostí jedného kontaktu za 10 minút (potom 429).
  • Udalosti nie sú idempotentné: opakovaná požiadavka zapíše udalosť znova. Košík posielajte vždy celý (opakovanie potom nič nepokazí), a čo sa má započítať len raz, pošlite ako objednávku.

Webhooky

Z Poštárny k vám

Účtové webhooky pošlú na vašu adresu nový kontakt, zmenu, odhlásenie, odoslanie, doručenie, otvorenie a klik. Registrácia, tvar dát a ukážkový príjemca: Webhooky a Recept: príjem webhookov.

Z e-shopu do Poštárny

WooCommerce aj Shopify vedia pri novej a zmenenej objednávke zavolať webhook, ale nevedia poslať hlavičku Authorization s API kľúčom. Preto medzi ne a Poštárnu patrí krátky prevodník na vašom serveri (alebo v Make, n8n či Zapier): overí podpis e-shopu, prevedie polia podľa tabuľky vyššie a zavolá POST /orders. Hotový plugin pre WooCommerce a modul pre Make a n8n zatiaľ nemáme.

PHP (WooCommerce, webhook Objednávka vytvorená a Objednávka aktualizovaná):

postarna-woo.php
<?php
// Secret of the WooCommerce webhook and the Poštárna API key come from the environment, never from the code.
$secret = getenv('WOO_WEBHOOK_SECRET');
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_WC_WEBHOOK_SIGNATURE'] ?? '';
if (!hash_equals(base64_encode(hash_hmac('sha256', $raw, $secret, true)), $sig)) {
    http_response_code(401);
    exit;
}
$o = json_decode($raw, true);
if (!isset($o['id'])) {            // the ping WooCommerce sends when the webhook is saved
    http_response_code(204);
    exit;
}
$items = [];
foreach ($o['line_items'] as $li) {
    $items[] = [
        'id' => $li['sku'] !== '' ? $li['sku'] : (string) $li['product_id'],
        'name' => $li['name'],
        'quantity' => $li['quantity'],
        'price' => ['withoutVat' => number_format((float) $li['price'], 2, '.', ''), 'currency' => $o['currency']],
    ];
}
$order = [
    'id' => (string) $o['id'],
    'shopId' => getenv('POSTARNA_SHOP_ID'),
    'eshopCode' => (string) $o['number'],
    'createdAt' => $o['date_created_gmt'] . 'Z',
    'paidAt' => $o['date_paid_gmt'] ? $o['date_paid_gmt'] . 'Z' : null,
    'status' => $o['status'],
    'email' => $o['billing']['email'] ?? null,
    'currency' => $o['currency'],
    'totalWithVat' => $o['total'],
    'totalWithoutVat' => number_format((float) $o['total'] - (float) $o['total_tax'], 2, '.', ''),
    'promoCodesUsed' => array_map(fn ($c) => $c['code'], $o['coupon_lines'] ?? []),
    'items' => $items,
];
$ch = curl_init('https://www.postarna.sk/api/v1/tenants/' . getenv('POSTARNA_TENANT') . '/orders');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 20,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('POSTARNA_API_KEY'), 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode($order),
]);
curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
// 5xx or 429: answer 500 so WooCommerce delivers the webhook again (the same order is idempotent).
http_response_code($status >= 500 || $status === 429 || $status === 0 ? 500 : 204);

JavaScript (Node.js 20+, Shopify webhook orders/create a orders/updated):

postarna-shopify.mjs
import crypto from 'node:crypto';
import http from 'node:http';

const { SHOPIFY_WEBHOOK_SECRET, POSTARNA_API_KEY, POSTARNA_TENANT, POSTARNA_SHOP_ID } = process.env;

function signed(raw, header) {
  const digest = crypto.createHmac('sha256', SHOPIFY_WEBHOOK_SECRET).update(raw).digest('base64');
  const a = Buffer.from(digest);
  const b = Buffer.from(header ?? '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

http.createServer((req, res) => {
  const chunks = [];
  req.on('data', (c) => chunks.push(c));
  req.on('end', async () => {
    const raw = Buffer.concat(chunks);
    if (!signed(raw, req.headers['x-shopify-hmac-sha256'])) return res.writeHead(401).end();
    const o = JSON.parse(raw.toString('utf8'));
    const order = {
      id: String(o.id),
      shopId: POSTARNA_SHOP_ID,
      eshopCode: o.name,
      createdAt: o.created_at,
      status: o.cancelled_at ? 'cancelled' : o.financial_status,
      email: o.email || null,
      currency: o.currency,
      totalWithVat: o.total_price,
      totalWithoutVat: (Number(o.total_price) - Number(o.total_tax || 0)).toFixed(2),
      promoCodesUsed: (o.discount_codes || []).map((d) => d.code),
      items: o.line_items.map((li) => ({
        id: li.sku || String(li.variant_id), name: li.title, quantity: li.quantity,
        price: { withVat: li.price, currency: o.currency },
      })),
    };
    const r = await fetch(`https://www.postarna.sk/api/v1/tenants/${POSTARNA_TENANT}/orders`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${POSTARNA_API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify(order),
    }).catch(() => ({ status: 0 }));
    // 5xx, 429 or no answer: 500 makes Shopify retry; the same order is idempotent
    res.writeHead(r.status >= 500 || r.status === 429 || r.status === 0 ? 500 : 204).end();
  });
}).listen(8080);
  • Prevodník odpovedá e-shopu rýchlo (do pár sekúnd), inak e-shop webhook zopakuje alebo vypne.
  • Pri chybe Poštárny (5xx, 429) vráťte e-shopu chybu, nech webhook zopakuje. Objednávka je idempotentná, opakovanie nič nezdvojí.
  • Chyba 400 znamená chybné dáta (message nesie názov poľa): opravte prevodník, opakovanie nepomôže.

Kontrola

  • GET /orders?contactId={id} ukáže objednávky kontaktu, GET /orders/{id} detail so stavom a konverziou.
  • GET /order-states v seen ukáže názvy stavov, ktoré prišli, a čo z nich mapa urobila. Nové názvy namapujte skôr, ako zapnete automatizácie po nákupe.
  • Automatizácie po nákupe: Recept: séria po nákupe; opustený košík: Recept: opustený košík.