# Webhooki Webhooki wysyłają zdarzenia do Twojego systemu w chwili, gdy następują, więc nie musisz odpytywać API. ## Zdarzenia | Zdarzenie | Kiedy | |---|---| | `booking.created` | Utworzono rezerwację (panel, import z kanału lub API) | | `booking.updated` | Zmieniły się daty, goście, godziny zameldowania/wymeldowania lub dane kontaktowe | | `booking.cancelled` | Rezerwacja została anulowana | | `checkin.completed` | Gość ukończył (lub zaktualizował) zameldowanie online | | `payment.completed` | Opłata turystyczna zapłacona lub kaucja zablokowana/zapłacona (także gdy personel oznaczy ją jako zapłaconą) | | `booking.unlocked` | Wszystkie kroki wykonane — przewodnik pobytu jest odblokowany | | `extras.ordered` | Gość zapłacił za dodatki — także za wcześniejsze zameldowanie lub późne wymeldowanie | | `message.sent` | Wysłano wiadomość do gościa przez kanał rezerwacji (Airbnb, Booking.com … przez Hostaway lub Channex) | | `feedback.received` | Gość ocenił pobyt (1–5) na stronie opinii po prośbie o recenzję | | `ping` | Zdarzenie testowe z przycisku **Test** | ### Co zawiera `data` | Zdarzenie | Pola `data` | |---|---| | `booking.created`, `booking.updated`, `booking.cancelled` | `bookingId`, `ref`, `source` (importy z kanałów); `reason: "stay-times"`, gdy kupione wcześniejsze zameldowanie / późne wymeldowanie zmieniło godziny | | `checkin.completed` | `bookingId`, `ref` | | `payment.completed` | `bookingId`, `leg` (`eco` lub `deposit`), `status` (`paid` lub `held`), `manual: true`, gdy personel oznaczył płatność jako zapłaconą | | `booking.unlocked` | `bookingId` | | `extras.ordered` | `bookingId`, `leg: "extras"`, `status: "paid"` | | `message.sent` | `bookingId`, `ref`, `via` (np. `Hostaway`) | | `feedback.received` | `bookingId`, `ref`, `rating` (1–5) | Wersja każdego zdarzenia do odczytu maszynowego — nagłówki, treść i `data` — znajduje się w sekcji `webhooks` [dokumentu OpenAPI](/api/v1/openapi.json) oraz w [dokumentacji API](/api/docs#webhooks). ## Dodaj webhook **Settings → Webhooks** (Ustawienia → Webhooki): wpisz adres URL `https://`, zaznacz wybrane zdarzenia (brak zaznaczenia = wszystkie) i kliknij **Add webhook**. **Sekret podpisu** (signing secret) jest pokazywany tylko raz — zapisz go w swoim odbiorniku. ![Settings → Webhooks](/site/img/docs/d-settings-webhooks.webp "Settings → Webhooks") ## Żądanie `POST` z treścią JSON: ``` { "id": "evt_…", "event": "payment.completed", "createdAt": "2026-11-18T14:03:11.000Z", "tenant": "t_…", "data": { "bookingId": "b_…", "leg": "eco", "status": "paid" } } ``` Nagłówki: | Nagłówek | Zawartość | |---|---| | `X-GP-Event` | nazwa zdarzenia | | `X-GP-Delivery` | unikalny identyfikator dostarczenia | | `X-GP-Timestamp` | czas Unix w sekundach | | `X-GP-Signature` | `v1=` + szesnastkowy HMAC-SHA256 z `.` z użyciem Twojego sekretu podpisu | ## Weryfikacja podpisu (Node.js) ``` const crypto = require('crypto'); function verify(rawBody, headers, secret) { const ts = headers['x-gp-timestamp']; const expected = 'v1=' + crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex'); const given = String(headers['x-gp-signature'] || ''); const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300; return fresh && given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected)); } ``` Zawsze weryfikuj względem **surowej** treści żądania i odrzucaj stare znaczniki czasu, aby zablokować ataki powtórzeniowe. ## Weryfikacja podpisu (C# / .NET) ``` using System.Security.Cryptography; using System.Text; static bool Verify(string rawBody, string ts, string signature, string secret) { if (!long.TryParse(ts, out var t) || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return false; using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var hex = Convert.ToHexString(h.ComputeHash(Encoding.UTF8.GetBytes(ts + "." + rawBody))).ToLowerInvariant(); return CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes("v1=" + hex), Encoding.ASCII.GetBytes(signature ?? "")); } ``` W .NET Framework zamiast `Convert.ToHexString` użyj `BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()`, a porównanie w stałym czasie wykonaj prostą pętlą. ## Weryfikacja podpisu (PHP) ``` $expected = 'v1=' . hash_hmac('sha256', $_SERVER['HTTP_X_GP_TIMESTAMP'] . '.' . file_get_contents('php://input'), $secret); $ok = abs(time() - (int)$_SERVER['HTTP_X_GP_TIMESTAMP']) < 300 && hash_equals($expected, $_SERVER['HTTP_X_GP_SIGNATURE'] ?? ''); ``` ## Dostarczanie i ponowienia Odpowiedz dowolnym statusem 2xx w ciągu 10 sekund. Nieudane dostarczenia są ponawiane po 30 sekundach i po 5 minutach (łącznie trzy próby). **Recent deliveries** pokazuje ostatnie 200 prób wraz z wynikiem. - `id` w treści i nagłówek `X-GP-Delivery` pozostają takie same przy każdym ponowieniu — zapisuj je i ignoruj duplikaty. - Zdarzenia mogą przychodzić w innej kolejności; korzystaj z `createdAt` i pobieraj rezerwację, aby poznać jej aktualny stan. - Przekierowania nie są obsługiwane, a wywoływane są wyłącznie publiczne adresy `https://` (bez sieci prywatnych i lokalnych). - Żądania mają nagłówki `Content-Type: application/json` i `User-Agent: GuestPortal-Webhooks/1`. ## Najczęściej zadawane pytania ### Czy w zdarzeniu otrzymuję pełną rezerwację? Zdarzenie zawiera identyfikatory i informację o zmianie. Pełną rezerwację pobierzesz przez `GET /api/v1/bookings/{id}` z użyciem klucza API — dzięki temu w treści webhooków nie są przesyłane żadne dane osobowe. ### Czy kanały mogą wysyłać webhooki do Guestbud? Tak — to kierunek odwrotny. Hostaway i Channex mogą natychmiast informować Guestbud o nowych i zmienionych rezerwacjach; zobacz [Hostaway](/docs/connect-hostaway) i [Channex](/docs/connect-channex). Te adresy przychodzące są tajne dla każdej firmy i wyzwalają jedynie ponowny import danej rezerwacji. ### Czy webhooki są wysyłane z konta demo? Nie.