Webhooks
Valós idejű értesítések booking eseményekről, HMAC-SHA256 aláírás ellenőrzés.
Áttekintés#
A Bokko webhook rendszer valós idejű HTTP értesítéseket küld, amikor egy foglalás állapota megváltozik. Így a te rendszered azonnal reagálhat az eseményekre anélkül, hogy folyamatosan lekérdeznie kellene az API-t.
- Valós idejű: az értesítés a státuszváltás pillanatában kiküldésre kerül
- HTTPS: csak biztonságos végpontra küldhető webhook
- HMAC-SHA256 aláírás: minden üzenettörzs kriptográfiailag aláírt, ellenőrizheted a hitelességét
Beállítás#
A webhook URL-t a PUT /v1/webhooks/config végponton tudod regisztrálni. Ehhez webhook.manage jogosultság szükséges.
curl -X PUT https://api.bokko.io/v1/webhooks/config \
-H "Authorization: Bearer {API_KEY}" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/webhooks/bokko" }'URL követelmények:
- Kötelezően HTTPS protokoll
- Maximum 500 karakter hosszú
- Nem tartalmazhat hitelesítő adatokat (user:pass@)
Tiltott címtartományok:
| Tartomány | Példák |
|---|---|
| Loopback | localhost, 127.0.0.1, ::1, 0.0.0.0 |
| Privát hálózat | 10.x.x.x, 172.16-31.x.x, 192.168.x.x |
| Link-local | 169.254.x.x |
PUT híváskor a rendszer automatikusan generál egy HMAC secretet (32 byte = 64 hex karakter). Ez a secret egyszer kerül visszaadásra a válaszban — mentsd el biztonságos helyre! Későbbi PUT hívások nem adják vissza újra.Események#
| Esemény | Leírás |
|---|---|
booking.requested | New appointment request received (always the request state, sequence 1 — an automatically accepted request is followed by booking.confirmed) |
booking.confirmed | Booking confirmed — by the business (acceptanceMode manual) or by the service's automatic acceptance rule (acceptanceMode automatic) |
booking.declined | Booking declined |
booking.cancelled | Booking cancelled |
booking.reschedule_proposed | Appointment modification proposed |
booking.reschedule_confirmed | >- |
booking.completed | Booking completed |
booking.no_show | Guest did not show up |
| Esemény | Leírás |
|---|---|
booking.requested | Új időpontkérés érkezett (mindig a kérés-állapot) |
booking.confirmed | Foglalás elfogadva — a szolgáltató által vagy a szolgáltatás automatikus elfogadási szabálya alapján |
booking.declined | Foglalás elutasítva |
booking.cancelled | Foglalás lemondva |
booking.reschedule_proposed | Időpont-módosítás javasolva |
booking.reschedule_confirmed | Módosított időpont megerősítve |
booking.completed | Foglalás teljesítve |
booking.no_show | Vendég nem jelent meg |
Üzenettörzs formátum#
Minden webhook kézbesítés egy JSON objektum a következő struktúrával:
deliveryId az üzleti eseményt azonosítja (esemény × foglalás × átmenet), nem a kísérletet: determinisztikus, 32 karakteres hex string. Ugyanannak az eseménynek minden kísérlete és minden újrakézbesítése ugyanezt az értéket hordozza — ez a deduplikációs kulcs.{
"event": "booking.confirmed",
"deliveryId": "3f2a9c0d8b7e4f1a2c3d4e5f6a7b8c9d",
"timestamp": "2026-04-01T09:00:00.000Z",
"salonSlug": "precision-cuts",
"transition": {
"id": "tr_8Kq2mZ4pX7",
"sequence": 2,
"occurredAt": "2026-04-01T09:15:00Z",
"from": "requested",
"to": "confirmed"
},
"booking": {
"bookingId": "abc123",
"publicReference": "BK-A2B3C4D5",
"status": "confirmed",
"locationId": "loc_belvaros_01",
"locationSnapshot": {
"id": "loc_belvaros_01",
"locationSlug": "belvaros",
"publicLocationName": "Bokko Belváros",
"address": {
"city": "Budapest",
"street": "Deák Ferenc utca 1.",
"zip": "1052"
},
"timezone": "Europe/Budapest"
},
"serviceId": "svc_haircut_01",
"serviceName": "Hajvágás",
"staffId": "staff_anna_01",
"staffName": "Anna",
"guestName": "Kiss Péter",
"requestedSlot": {
"date": "2026-04-05",
"startTime": "10:00",
"timezone": "Europe/Budapest"
},
"confirmedSlot": {
"date": "2026-04-05",
"startTime": "10:00",
"endTime": "10:45",
"timezone": "Europe/Budapest"
},
"note": null,
"createdAt": "2026-04-01T09:00:00Z",
"updatedAt": "2026-04-01T09:15:00Z",
"stateVersion": 2,
"acceptanceMode": "manual"
}
}A confirmedSlot a véglegesített időpontot az aktív, megerősített jellegű életciklus-állapotoknál tartalmazza, például confirmed, completed és noShow esetén. cancelled vagy declined státuszú foglalásoknál a confirmedSlot az API-ban mindig null, akkor is, ha a foglalás korábban már meg volt erősítve. A status mező alapján dől el, hogy az időpont jelenleg foglalt-e.
Sorrend, identitás és deduplikáció#
Három külön kulcs — egyik sem helyettesíti a másikat:
| Szerep | Kulcs | Szabály |
|---|---|---|
| Állapot-rendezés | transition.sequence (= booking.stateVersion) | A kézbesítési sorrend nem garantált. Foglalásonként csak akkor alkalmazz egy eseményt, ha a sequence-e nagyobb a legutóbb alkalmazottnál; különben elavult. |
| Üzleti mutáció identitása | transition.id | Minden állapotváltozás (státusz-átmenet vagy a megerősített időpont módosítása) új, változtathatatlan azonosítót kap; létrehozásnál null. Nem rendezhető. |
| Kézbesítés-deduplikáció | deliveryId | Ugyanannak az eseménynek minden kísérlete és újrakézbesítése azonos. |
A timestamp és a transition.occurredAt csak tájékoztató — egy tranzakció két eseménye azonos időt hordozhat. Minden foglalási webhook állapotváltozást jelent, ezért minden esemény hordoz sequence-et. Egy megerősített foglalás két egymást követő időpont-módosítása két booking.reschedule_confirmed eseményt ad N és N+1 sequence-szel és két különböző deliveryId-val.
Automatikus elfogadás. Ha a szolgáltatás elfogadási szabálya ugyanabban a hívásban elfogadja a kérést, két esemény megy ki: booking.requested (sequence: 1, status: requested, üres confirmedSlot) és booking.confirmed (sequence: 2, acceptanceMode: automatic). Minden payload a SAJÁT eseménye állapotát írja le, nem a foglalás aktuális állapotát.
Status enum ↔ eseménynév leképezés#
A foglalás status mezője camelCase enum-ot használ (Bokko Public API konvenció), míg a webhook eseménynevei snake_case névteres formátumot (REST / event sourcing konvenció). A kettő szándékosan eltér egymástól — klienseknek az alábbi leképezést kell alkalmazni:
| BookingStatus enum | Webhook esemény |
|---|---|
requested | booking.requested (foglalás létrejött) |
confirmed | booking.confirmed, booking.reschedule_confirmed (ha átütemezés után erősítik meg) |
declined | booking.declined |
cancelled | booking.cancelled |
rescheduleProposed | booking.reschedule_proposed |
completed | booking.completed |
noShow | booking.no_show |
booking.no_show esemény neve snake_case-ben szerepel (no_show), nem camelCase-ben (noShow). A Booking schema status mezője viszont camelCase-t használ (noShow, rescheduleProposed). Ez az eltérés szándékos design döntés.HTTP fejlécek#
Minden webhook kérés az alábbi egyedi fejléceket tartalmazza:
| Fejléc | Leírás |
|---|---|
X-Bokko-Signature | sha256=<hex> — HMAC-SHA256 aláírás a body felett |
X-Bokko-Delivery-Id | Üzleti esemény-azonosító — deduplikációs kulcs, minden kísérleten azonos |
X-Bokko-Event | Az esemény neve (pl. booking.confirmed) |
X-Bokko-Timestamp | Az első kiküldés ISO 8601 időbélyege, minden kísérleten azonos |
X-Bokko-Attempt | A kísérlet sorszáma (1–3) |
Aláírás ellenőrzés#
Node.js#
const crypto = require('crypto');
function verifySignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
const sig = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(sig, 'hex'),
Buffer.from(expected, 'hex')
);
}Python#
import hmac
import hashlib
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
sig = signature.replace('sha256=', '')
return hmac.compare_digest(sig, expected)Kézbesítési szabályok#
| Szabály | Érték |
|---|---|
| Kísérletek száma | eseményenként legfeljebb 3 |
| Timeout | kísérletenként 8 másodperc |
| Redirect követés | Nem |
| Kézbesítési garancia | At-least-once — ugyanaz az esemény többször is megérkezhet |
| Újrapróbált válaszok | timeout, hálózati hiba, 408, 429, 5xx |
| Nem újrapróbált | minden más státusz, a 3xx is |
| Ütemezés | 2. kísérlet az 1. után ~15 mp – 2 perccel, 3. kísérlet a 2. után ~2–6 perccel |
Retry-After | 429 / 503 esetén figyelembe vesszük (másodperc vagy HTTP-dátum, max. 600 mp); csak nyújthatja a várakozást |
Minden kísérlet bájtra azonos törzset küld ugyanazzal a deliveryId-val és timestamp-pel. Az aláírás kísérletenként az aktuális secrettel készül, és az újraküldés az aktuálisan beállított URL-re megy — secret-rotálás után az újraküldött kézbesítést már az új secret írja alá.
Lehetséges kimenetek:
success— 2xx válaszkód érkezetttimeout— a végpont nem válaszolt 8 másodpercen belülnetwork_error— a végpont nem volt elérhetőhttp_error— nem 2xx válaszkód (pl. 500)
exhausted (újrapróbálható hiba az utolsó kísérleten) vagy failed (nem újrapróbálható válasz, vagy kikapcsolt webhook-beállítás) állapotú kézbesítést nem próbáljuk újra. Ezeket a kézbesítési naplóból figyeld, és szükség esetén egyeztess a GET /v1/bookings végponttal.Kézbesítési napló#
A GET /v1/webhooks/deliveries végpont üzleti eseményenként egy rekordot ad: a kísérletek az attempts listában, a kézbesítés állapota a status mezőben (processing, retry_scheduled, succeeded, failed, exhausted). A rekordokat 12 hónapig őrizzük; egy lekérdezés legfeljebb 90 napot fedhet le.
Szűrési lehetőségek:
| Paraméter | Leírás |
|---|---|
event | Eseménytípus szűrő (pl. booking.confirmed) |
outcome | A legutóbbi kísérlet kimenete (success, timeout, network_error, http_error) |
from / to | Dátumtartomány (ISO 8601) |
A lapozás cursor-alapú: a válasz meta.cursor mezőjét add meg a következő kérés cursor paraméterében.
Secret rotálás#
Ha a secreted kompromittálódott, új secretet generálhatsz:
curl -X POST https://api.bokko.io/v1/webhooks/config/rotate-secret \
-H "Authorization: Bearer {API_KEY}"Legjobb gyakorlatok#
- Rendezz
transition.sequenceszerint, deduplikáljdeliveryIdszerint — lásd Sorrend, identitás és deduplikáció. - Dolgozz fel idempotensen,
deliveryIdalapján — a kézbesítés at-least-once, tehát ugyanaz az esemény újra megérkezhet (például ha a 200-as válaszod egy timeout miatt elveszett). Tárold a feldolgozottdeliveryId-kat, és az ismétlést200-zal nyugtázd újrafeldolgozás nélkül - Válaszolj gyorsan 200-zal, a tényleges feldolgozást aszinkron végezd — a 8 másodperces timeout szoros lehet összetett logikánál
- Periodikusan egyeztesd a
GET /v1/bookingsvégponttal, hogy ne maradj le eseményekről - Helyi fejlesztéshez használj webhook.site-ot vagy ngrok-ot a webhook fogadásához