Webhookok
A Terminuj HTTP POST kérést küld JSON-nal az Ön címére az Ön által kiválasztott eseményeknél (új foglalás, lemondás, áthelyezés…). Minden kérés HMAC SHA-256 aláírással rendelkezik — feldolgozás előtt ellenőrizze az aláírást.
1. Beállítás
Az irányítópulton → Beállítások → Webhookok → + Új webhook:
- URL: az Ön címe, csak
https:// - Események: egy vagy több (lásd a 4. részt)
- Titkos kulcs: mi generáljuk (
whsec_…), és egyszer mutatjuk meg — mentse el - Próbálkozások száma: 1–10, alapértelmezés 5
2. Kézbesítés és újrapróbálás
- A küldés percenként fut — az esemény általában egy percen belül megérkezik.
- A válaszra 10 másodpercig várunk; siker = HTTP
2xx. - Bármilyen más válasz, időtúllépés vagy hálózati hiba esetén újrapróbáljuk: az 1. próbálkozás azonnal, majd 1 perc, 5 perc, 30 perc és 2 óra után (5-nél több próbálkozásnál minden további 2 óra múlva).
- Az utolsó próbálkozás után a kézbesítés „sikertelen” — a webhook részleteinél az Újraküldés gombbal küldheti el újra.
- Fejlécek:
X-Rezervacia-Event,X-Rezervacia-Delivery-Id,X-Rezervacia-Signature. Ugyanannak a kézbesítésnek az ismétlései azonosX-Rezervacia-Delivery-Idértékkel érkeznek — ez alapján csak egyszer dolgozza fel az eseményt.
3. Az aláírás ellenőrzése (HMAC SHA-256)
Minden kérés tartalmazza az X-Rezervacia-Signature fejlécet sha256=<hex> formátumban. A HMAC-et a kérés feldolgozatlan törzséből számítsa ki (ne a feldolgozott JSON-ból), és állandó idejű összehasonlítással (timing-safe) vesse össze.
Node.js (Express)
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) // nyers törzs (Buffer) — NEM feldolgozott 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());
// Az ismételt kézbesítés azonos X-Rezervacia-Delivery-Id értékkel érkezik — csak egyszer dolgozza fel.
console.log('Event:', event.event, 'Delivery:', req.headers['x-rezervacia-delivery-id'], 'Data:', event.data);
res.status(200).send('ok');
});Python (Flask)
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() # nyers bájtok — ellenőrzés előtt NE dolgozza fel a JSON-t!
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', 200PHP
<?php
$secret = getenv('REZERVACIA_WEBHOOK_SECRET');
$signature = $_SERVER['HTTP_X_REZERVACIA_SIGNATURE'] ?? '';
$body = file_get_contents('php://input'); // nyers — NEM $_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';Ruby (Sinatra)
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⚠ SOHA ne a feldolgozott JSON-ból ellenőrizze az aláírást. A könyvtárak eltérően szerializálják a JSON-t (szóközök, kulcsok sorrendje) — mindig a kérés feldolgozatlan törzsét (raw body) használja.
4. Események
BOOKING_CREATEDÚj foglalás — a foglalási űrlapról, kézi rögzítéssel az irányítópulton vagy API-n keresztül
Mezők a
dataobjektumban: id, publicReference, confirmationCode, startAt, source (web / embed / manual)BOOKING_CANCELLEDLemondás — az ügyfél, a vállalkozás az irányítópulton vagy API-n keresztül
Mezők a
dataobjektumban: id; útvonaltól függően publicReference, reason / cancellationReason, cancelledBy, refund, cancelledAt, viaBOOKING_RESCHEDULEDIdőpont áthelyezése — az ügyfél, az irányítópulton vagy API-n keresztül
Mezők a
dataobjektumban: id; ügyfél: oldStartAt, newStartAt · irányítópult és API: previousStartAt, startAt, endAt, viaBOOKING_COMPLETEDA vállalkozás teljesítettnek jelöli az időpontot, vagy automatikusan az időpont után
Mezők a
dataobjektumban: id, publicReference; automatikus és tömeges módosításnál completedAt / markedAt, viaBOOKING_NO_SHOWA vállalkozás megjelöli, hogy az ügyfél nem jelent meg
Mezők a
dataobjektumban: id, publicReferenceCUSTOMER_CREATEDÚj ügyfél első foglalása a foglalási űrlapon keresztül
Mezők a
dataobjektumban: customer { fullName, email, phone }, firstBookingId, sourceBOOKING_PAIDEgyelőre nem küldjük — kiválasztható, de a Terminuj ezt az eseményt jelenleg nem küldi el
Mezők a
dataobjektumban: —
5. Az üzenet felépítése
Minden esemény azonos burkolatban érkezik: { event, occurredAt, tenantId, data }. A data tartalma eseményenként és aszerint eltér, honnan érkezett a változás (4. rész); mindig tartalmazza a foglalás id azonosítóját (CUSTOMER_CREATED esetén firstBookingId).
BOOKING_CREATED (foglalási űrlap)
{
"event": "BOOKING_CREATED",
"occurredAt": "2026-06-10T10:30:00.000Z",
"tenantId": "a vállalkozás uuid-ja",
"data": {
"id": "a foglalás uuid-ja",
"publicReference": "BK4F2H8K9M3N7P",
"confirmationCode": "K7M2QX",
"startAt": "2026-06-15T14:00:00.000Z",
"source": "embed"
}
}BOOKING_CANCELLED (az ügyfél mondta le)
{
"event": "BOOKING_CANCELLED",
"occurredAt": "2026-06-12T08:15:00.000Z",
"tenantId": "a vállalkozás uuid-ja",
"data": {
"id": "a foglalás uuid-ja",
"publicReference": "BK4F2H8K9M3N7P",
"reason": "Betegség",
"cancelledBy": "customer",
"refund": null
}
}6. Tesztelés
A webhookok listájában a Teszt gomb próba BOOKING_CREATED eseményt küld érvényes aláírással és "test": true jelzéssel a burkolatban — kitalált foglalás, semmi sem mentődik. Éles használat előtti ellenőrzésre ajánlott.