Webhookok
A szolgáltatás két aláírt webhookkal értesíti a szervered a rendelésekről. A kézbesítés legalább egyszeri, ezért a fogadód ellenőrzi az aláírást, az eseményazonosító alapján deduplikál, és gyorsan válaszol.
Az elérési utak, mezőnevek, hibakódok, fejlécnevek és kódpéldák angolul maradnak: ezek jelentik a szerződést.
Események
Az előfizetéshez tedd közzé a channels.webhooks[] tömböt a webshop konfigurációjában (egy id, egy url és az events). Az URL-nek publikus https címnek kell lennie; a privát, loopback és belső címeket a konfiguráció mentésekor, majd küldéskor is elutasítjuk.
| Esemény | Küldés ideje |
|---|---|
| order.created | Egy éles rendelést eltároltunk (POST /cfg/v1/order). |
| order.status_changed | A konzolban egy rendelést másik státuszba tettél. |
A törzs
A törzs kanonikus JSON (RFC 8785), és minden próbálkozásnál bájtra azonos. A data.order a teljes aláírt order.v1 dokumentum; a data.fromStatus csak az order.status_changed eseményen szerepel.
{
"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"
}Fejlécek
Minden kézbesítés öt fejlécet hordoz. A CutOptim-Event-Id alapján deduplikálj: az újrapróbálkozás és az újraküldés megismétli, míg a CutOptim-Delivery-Id különbözik.
| Fejléc | Leírás |
|---|---|
| CutOptim-Signature | Az időbélyeg és a nyers törzs egy vagy két HMAC-SHA256 aláírása. |
| CutOptim-Event-Id | Az eseményazonosító, megegyezik a törzs azonosítójával. Ez alapján deduplikálj. |
| CutOptim-Event-Type | Az esemény neve. |
| CutOptim-Delivery-Id | Ez a kézbesítés; az újrapróbálkozás vagy az újraküldés újat kap. |
| CutOptim-Delivery-Attempt | A próbálkozás sorszáma, 1-től indul. |
Az aláírás ellenőrzése
Az aláírás t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>. Fogadd el a kézbesítést, ha bármelyik v1 érték egyezik valamelyik titkoddal, és a t legfeljebb 300 másodpercre van az órádtól; az összehasonlítást állandó időben végezd. A nyers bájtokat ellenőrizd, mielőtt feldolgoznád őket: az a keretrendszer, amely értelmezi és újra szerializálja a törzset, megváltoztatja azt, és minden ellenőrzés elbukik.
Az alábbi két fogadó a változatlan referenciapélda: Node 18+ csak a node:crypto használatával, és PHP 7.4+ Composer nélkül. Mindkettő a titkot a CUTOPTIM_WEBHOOK_SIGNING változóból olvassa.
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']]);
Újrapróbálkozások, dead letterek és újraküldés
A nem 2xx válasz, az időtúllépés és az átirányítás sikertelen próbálkozásnak számít. Egy kézbesítést összesen 6 alkalommal próbálunk meg, egyre hosszabb várakozással; az utolsó kudarc után dead letter lesz. Az az URL, amely privát címre oldódik fel, azonnal dead letter. A szervered a titkos kulccsal olvashatja a kézbesítési naplót és kérhet újraküldést (/cfg/v1/admin/deliveries); az újraküldés megtartja az eseményazonosítót.
| Ennyi próbálkozás után | Következő próba |
|---|---|
| 1 | 1 min |
| 2 | 5 min |
| 3 | 25 min |
| 4 | 2.08 h |
| 5 | 6 h |
Teszt rendelések
A teszt kulccsal leadott rendelés nem tesz sorba webhookot és e-mailt: az ERP-d és a vásárlóid soha nem látnak tesztforgalmat.
Szándékosan nincs itt "kipróbálom" gomb: a titkos kulcs a szerveredre való, soha nem a böngészőbe. A demó webshop elejétől végéig megmutatja a beágyazást és a rendelés átadását.
Jogi tudnivalókKonfigurátor feltételek (EN) ·Adatfeldolgozási szerződés (EN) ·Vásárlói adatvédelmi tájékoztató ·Harmadik féltől származó licencek (EN)