Přejít na hlavní obsah
← Reference API

Webhooky

Služba informuje váš server o objednávkách dvěma podepsanými webhooky. Doručení proběhne alespoň jednou, takže váš přijímač ověří podpis, deduplikuje podle id události a odpoví rychle.

Cesty, názvy polí, chybové kódy, názvy hlaviček a ukázky kódu zůstávají anglicky: jsou součástí kontraktu.

Události

Odběr nastavíte zveřejněním channels.webhooks[] v konfiguraci obchodu (id, url a events). URL musí být veřejné https; soukromé, loopback a interní adresy se zamítnou při uložení konfigurace i znovu při odeslání.

UdálostOdesláno, když
order.createdOstrá objednávka byla uložena (POST /cfg/v1/order).
order.status_changedV konzoli jste převedli objednávku do jiného stavu.

Tělo

Tělo je kanonický JSON (RFC 8785) a při každém pokusu je bajt od bajtu stejné. data.order je úplný podepsaný dokument order.v1; data.fromStatus existuje jen u order.status_changed.

{
  "createdAt": "2026-10-19T09:30:00.000Z",
  "data": {
    "fromStatus": "received",
    "order": {
      "…": "order.v1"
    },
    "orderId": "<order id>",
    "status": "confirmed"
  },
  "id": "<event id>",
  "schemaVersion": 1,
  "tenantId": "<tenant id>",
  "type": "order.status_changed"
}

Hlavičky

Každé doručení nese pět hlaviček. Deduplikujte podle CutOptim-Event-Id: opakování i opětovné doručení jej zachovají, zatímco CutOptim-Delivery-Id se liší.

HlavičkaPopis
CutOptim-SignatureČasová značka a jeden či dva podpisy HMAC-SHA256 surového těla.
CutOptim-Event-IdId události, shodné s id v těle. Deduplikujte podle něj.
CutOptim-Event-TypeNázev události.
CutOptim-Delivery-IdToto doručení; opakování nebo opětovné doručení dostane nové.
CutOptim-Delivery-AttemptČíslo pokusu, počínaje 1.

Ověření podpisu

Podpis má tvar t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>. Doručení přijměte, pokud kterákoli hodnota v1 odpovídá některému z vašich tajemství a t leží do 300 sekund od vašich hodin; porovnávejte v konstantním čase. Surové bajty ověřte dřív, než je rozparsujete: framework, který tělo rozparsuje a znovu serializuje, jej změní a každá kontrola selže.

Oba přijímače níže jsou referenční příklady, beze změny: Node 18+ jen s node:crypto a PHP 7.4+ bez Composeru. Oba čtou tajemství z CUTOPTIM_WEBHOOK_SIGNING.

configurator/adapters/reference/node/example-server.mjs

// example-server.mjs - a webhook endpoint on node:http, to read and adapt.   CUTOPTIM_WEBHOOK_SIGNING=whsec_... node example-server.mjs
// (whsec_REPLACE_ME is a placeholder: the real secret comes from the CutOptim admin route.)
import { createServer } from 'node:http';
import { createWebhookReceiver } from './cutoptim-verify.mjs';

const secret = process.env.CUTOPTIM_WEBHOOK_SIGNING;
if (!secret) { console.error('set CUTOPTIM_WEBHOOK_SIGNING (whsec_REPLACE_ME is a placeholder)'); process.exit(1); }

const receive = createWebhookReceiver({
  secrets: [secret],
  async onEvent(event) {
    // event.type === 'order.created'; event.data.order is the signed order document. Store it in YOUR order table here,
    // keyed by event.data.orderId, in the same transaction that records the event id as seen.
    console.log('order.created', event.data.orderId);
  },
});

createServer(async (req, res) => {
  if (req.method !== 'POST') { res.writeHead(405).end(); return; }
  const chunks = []; let size = 0;
  for await (const c of req) { size += c.length; if (size > 2_000_000) { res.writeHead(413).end(); return; } chunks.push(c); }
  const out = await receive({ rawBody: Buffer.concat(chunks), headers: req.headers });   // the RAW bytes, not parsed JSON
  res.writeHead(out.status, { 'Content-Type': 'application/json' }).end(JSON.stringify({ result: out.reason }));
}).listen(Number(process.env.PORT ?? 8787));

configurator/adapters/reference/php/example-webhook.php

<?php
/**
 * example-webhook.php - a webhook endpoint in plain PHP, to read and adapt.
 * CUTOPTIM_WEBHOOK_SIGNING=whsec_... is read from the environment (whsec_REPLACE_ME is a placeholder; the real secret comes from the
 * CutOptim admin route). Replace MemorySeen with a table that has a UNIQUE key on the event id.
 */
require __DIR__ . '/CutOptimVerify.php';

use CutOptim\CutOptimVerify;
use CutOptim\MemorySeen;

$secret = getenv('CUTOPTIM_WEBHOOK_SIGNING');
if ($secret === false || $secret === '') {
    http_response_code(500);
    exit('CUTOPTIM_WEBHOOK_SIGNING is not set');
}

$rawBody = file_get_contents('php://input');   // the RAW bytes, not decoded JSON
$headers = function_exists('getallheaders') ? getallheaders() : [];
$out = CutOptimVerify::handleWebhook($rawBody, $headers, [$secret], new MemorySeen(), static function (array $event): void {
    // $event['type'] === 'order.created'; $event['data']['order'] is the signed order document. Store it in YOUR order table
    // keyed by $event['data']['orderId'], in the same transaction that records the event id as seen.
});

http_response_code($out['status']);
header('Content-Type: application/json');
echo json_encode(['result' => $out['reason']]);

Opakování, nedoručené zprávy a opětovné doručení

Odpověď jiná než 2xx, vypršení času i přesměrování jsou neúspěšné pokusy. Doručení se zkouší celkem 6krát, s rostoucími prodlevami; po posledním selhání se stává nedoručenou zprávou. URL, které se přeloží na soukromou adresu, je nedoručenou zprávou okamžitě. Váš server si může přečíst protokol doručení a pomocí tajného klíče požádat o opětovné doručení (/cfg/v1/admin/deliveries); opětovné doručení zachovává id události.

Po pokusuDalší pokus za
11 min
25 min
325 min
42.08 h
56 h

Testovací objednávky

Objednávka zadaná s testovacím klíčem nezařadí žádný webhook ani e-mail: váš ERP a vaši zákazníci testovací provoz nikdy neuvidí.

Tlačítko „vyzkoušet“ tu záměrně není: tajný klíč patří na váš server, nikdy do prohlížeče. Demo obchod ukazuje vložení i předání objednávky v provozu od začátku do konce.

Právní informacePodmínky konfigurátoru (EN) ·Smlouva o zpracování údajů (EN) ·Zásady ochrany osobních údajů pro kupující ·Licence třetích stran (EN)