# Webhooks Webhooks senden Events in dem Moment an Ihr System, in dem sie eintreten – Sie müssen die API also nicht abfragen (Polling). ## Events | Event | Wann | |---|---| | `booking.created` | Eine Buchung wurde angelegt (Dashboard, Kanalimport oder API) | | `booking.updated` | Daten, Gäste, Check-in-/Check-out-Zeiten oder Kontaktdaten haben sich geändert | | `booking.cancelled` | Eine Buchung wurde storniert | | `checkin.completed` | Der Gast hat den Online-Check-in abgeschlossen (oder aktualisiert) | | `payment.completed` | Touristensteuer bezahlt oder Kaution reserviert/bezahlt (auch wenn Mitarbeitende sie als bezahlt markieren) | | `booking.unlocked` | Alle Schritte sind erledigt – die digitale Gästemappe ist freigeschaltet | | `extras.ordered` | Der Gast hat Extras bezahlt – auch einen frühen Check-in oder späten Check-out | | `message.sent` | Eine Nachricht wurde über den Buchungskanal an den Gast gesendet (Airbnb, Booking.com … über Hostaway oder Channex) | | `feedback.received` | Der Gast hat den Aufenthalt nach einer Bewertungsanfrage auf der Feedback-Seite bewertet (1–5) | | `ping` | Test-Event über den Button **Test** | ### Was `data` enthält | Event | `data`-Felder | |---|---| | `booking.created`, `booking.updated`, `booking.cancelled` | `bookingId`, `ref`, `source` (Kanalimporte); `reason: "stay-times"`, wenn ein gekaufter früher Check-in / später Check-out die Zeiten verschoben hat | | `checkin.completed` | `bookingId`, `ref` | | `payment.completed` | `bookingId`, `leg` (`eco` oder `deposit`), `status` (`paid` oder `held`), `manual: true`, wenn Mitarbeitende sie als bezahlt markiert haben | | `booking.unlocked` | `bookingId` | | `extras.ordered` | `bookingId`, `leg: "extras"`, `status: "paid"` | | `message.sent` | `bookingId`, `ref`, `via` (z. B. `Hostaway`) | | `feedback.received` | `bookingId`, `ref`, `rating` (1–5) | Die maschinenlesbare Fassung jedes Events – Header, Body und `data` – finden Sie im Abschnitt `webhooks` des [OpenAPI-Dokuments](/api/v1/openapi.json) und in der [API-Referenz](/api/docs#webhooks). ## Webhook hinzufügen **Settings → Webhooks**: Geben Sie eine `https://`-URL ein, wählen Sie die gewünschten Events (keine Auswahl = alle) und klicken Sie auf **Add webhook**. Das **Signing Secret** wird nur einmal angezeigt – speichern Sie es bei Ihrem Empfänger. ![Settings → Webhooks](/site/img/docs/d-settings-webhooks.webp "Settings → Webhooks") ## Die Anfrage `POST` mit JSON-Body: ``` { "id": "evt_…", "event": "payment.completed", "createdAt": "2026-11-18T14:03:11.000Z", "tenant": "t_…", "data": { "bookingId": "b_…", "leg": "eco", "status": "paid" } } ``` Header: | Header | Inhalt | |---|---| | `X-GP-Event` | der Event-Name | | `X-GP-Delivery` | eindeutige Zustellungs-ID | | `X-GP-Timestamp` | Unix-Zeit in Sekunden | | `X-GP-Signature` | `v1=` + hexadezimaler HMAC-SHA256 von `.` mit Ihrem Signing Secret | ## Signatur prüfen (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)); } ``` Prüfen Sie immer gegen den **unveränderten** (rohen) Request-Body und lehnen Sie alte Zeitstempel ab, um Replay-Angriffe zu verhindern. ## Signatur prüfen (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 ?? "")); } ``` Unter .NET Framework verwenden Sie `BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()` statt `Convert.ToHexString` und vergleichen in konstanter Zeit mit einer einfachen Schleife. ## Signatur prüfen (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'] ?? ''); ``` ## Zustellung und Wiederholungen Antworten Sie innerhalb von 10 Sekunden mit einem beliebigen 2xx-Status. Fehlgeschlagene Zustellungen werden nach 30 Sekunden und nach 5 Minuten wiederholt (insgesamt drei Versuche). **Recent deliveries** zeigt die letzten 200 Versuche mit ihrem Ergebnis. - `id` im Body und der Header `X-GP-Delivery` bleiben bei jeder Wiederholung gleich – speichern Sie den Wert und ignorieren Sie Duplikate. - Events können in anderer Reihenfolge eintreffen; verwenden Sie `createdAt` und rufen Sie die Buchung ab, um den aktuellen Stand zu erhalten. - Weiterleitungen werden nicht verfolgt, und es werden nur öffentliche `https://`-Adressen aufgerufen (keine privaten oder lokalen Netzwerke). - Anfragen kommen mit `Content-Type: application/json` und `User-Agent: GuestPortal-Webhooks/1`. ## Häufige Fragen ### Erhalte ich die vollständige Buchung im Event? Das Event enthält IDs und die Änderung. Rufen Sie die vollständige Buchung mit `GET /api/v1/bookings/{id}` und einem API-Schlüssel ab – so werden keine personenbezogenen Daten in Webhook-Bodies übertragen. ### Können Kanäle Webhooks an Guestbud senden? Ja – das ist die andere Richtung. Hostaway und Channex können Guestbud sofort über neue und geänderte Reservierungen informieren; siehe [Hostaway](/docs/connect-hostaway) und [Channex](/docs/connect-channex). Diese eingehenden Adressen sind pro Unternehmen geheim und lösen nur einen neuen Import der jeweiligen Reservierung aus. ### Werden aus dem Demo-Konto Webhooks gesendet? Nein.