Terminuj pošle HTTP POST s JSON na vašu adresu pri udalostiach, ktoré si vyberiete (nová rezervácia, zrušenie, presun…). Každá požiadavka je podpísaná HMAC SHA-256 — podpis overte pred spracovaním.
V nástenke → Nastavenia → Webhooks → + Nový webhook:
https://whsec_…) a ukážeme raz — uložte si ho2xx.X-Rezervacia-Event, X-Rezervacia-Delivery-Id, X-Rezervacia-Signature. Opakovania toho istého doručenia majú rovnaké X-Rezervacia-Delivery-Id — podľa neho spracujte udalosť len raz.Každý request obsahuje header X-Rezervacia-Signature v formáte sha256=<hex>. Vypočítajte HMAC z RAW body (NIE parsed JSON) a porovnajte timing-safe.
const crypto = require('crypto');
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-rezervacia-signature']; // "sha256=<hex>"
const secret = process.env.REZERVACIA_WEBHOOK_SECRET;
const computed = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.body) // Buffer raw body — NIE parsed JSON!
.digest('hex');
const a = Buffer.from(signature || ''), b = Buffer.from(computed);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send('invalid signature');
}
const event = JSON.parse(req.body.toString());
// Opakované doručenie má rovnaké X-Rezervacia-Delivery-Id — spracujte ho len raz.
console.log('Event:', event.event, 'Delivery:', req.headers['x-rezervacia-delivery-id'], 'Data:', event.data);
res.status(200).send('ok');
});import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ['REZERVACIA_WEBHOOK_SECRET'].encode()
@app.post('/webhook')
def webhook():
signature = request.headers.get('X-Rezervacia-Signature', '')
body = request.get_data() # raw bytes — NEPARSOVAŤ JSON pred verifikáciou!
computed = 'sha256=' + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, computed):
abort(401)
event = request.get_json()
print('Event:', event['event'], 'Data:', event['data'])
return 'ok', 200<?php
$secret = getenv('REZERVACIA_WEBHOOK_SECRET');
$signature = $_SERVER['HTTP_X_REZERVACIA_SIGNATURE'] ?? '';
$body = file_get_contents('php://input'); // raw — NIE $_POST!
$computed = 'sha256=' . hash_hmac('sha256', $body, $secret);
if (!hash_equals($signature, $computed)) {
http_response_code(401);
exit('invalid signature');
}
$event = json_decode($body, true);
error_log('Event: ' . $event['event']);
http_response_code(200);
echo 'ok';require 'sinatra'
require 'openssl'
SECRET = ENV.fetch('REZERVACIA_WEBHOOK_SECRET')
post '/webhook' do
signature = request.env['HTTP_X_REZERVACIA_SIGNATURE'] || ''
body = request.body.read
computed = 'sha256=' + OpenSSL::HMAC.hexdigest('sha256', SECRET, body)
halt 401 unless Rack::Utils.secure_compare(signature, computed)
event = JSON.parse(body)
puts "Event: #{event['event']}"
status 200
body 'ok'
end⚠ NIKDY neverifikujte podpis z parsed JSON. JSON serialization medzi knižnicami sa líši v whitespace / key order — vždy použite RAW body.
BOOKING_CREATEDNová rezervácia — z rezervačného formulára, ručný zápis v nástenke alebo cez API
Polia v data: id, publicReference, confirmationCode, startAt, source (web / embed / manual)
BOOKING_CANCELLEDZrušenie — zákazníkom, prevádzkou v nástenke alebo cez API
Polia v data: id; podľa cesty publicReference, reason / cancellationReason, cancelledBy, refund, cancelledAt, via
BOOKING_RESCHEDULEDPresun termínu — zákazníkom, v nástenke alebo cez API
Polia v data: id; zákazník: oldStartAt, newStartAt · nástenka a API: previousStartAt, startAt, endAt, via
BOOKING_COMPLETEDPrevádzka označí termín ako dokončený, alebo automaticky po termíne
Polia v data: id, publicReference; pri automatike a hromadnej zmene completedAt / markedAt, via
BOOKING_NO_SHOWPrevádzka označí neúčasť zákazníka
Polia v data: id, publicReference
CUSTOMER_CREATEDPrvá rezervácia nového zákazníka cez rezervačný formulár
Polia v data: customer { fullName, email, phone }, firstBookingId, source
BOOKING_PAIDZatiaľ sa neposiela — dá sa vybrať, ale Terminuj túto udalosť dnes neodosiela
Polia v data: —
Každá udalosť má rovnaký obal: { event, occurredAt, tenantId, data }. Obsah data sa líši podľa udalosti a podľa toho, odkiaľ zmena prišla (časť 4); vždy obsahuje id rezervácie (pri CUSTOMER_CREATED firstBookingId).
{
"event": "BOOKING_CREATED",
"occurredAt": "2026-06-10T10:30:00.000Z",
"tenantId": "uuid prevádzky",
"data": {
"id": "uuid rezervácie",
"publicReference": "BK4F2H8K9M3N7P",
"confirmationCode": "K7M2QX",
"startAt": "2026-06-15T14:00:00.000Z",
"source": "embed"
}
}{
"event": "BOOKING_CANCELLED",
"occurredAt": "2026-06-12T08:15:00.000Z",
"tenantId": "uuid prevádzky",
"data": {
"id": "uuid rezervácie",
"publicReference": "BK4F2H8K9M3N7P",
"reason": "Choroba",
"cancelledBy": "customer",
"refund": null
}
}V zozname webhookov tlačidlo Test pošle skúšobnú BOOKING_CREATED so správnym podpisom a s "test": true v obale — vymyslená rezervácia, nič sa nezapíše. Vhodné na overenie pred ostrou prevádzkou.