Skip to main content

Bookings & Webhooks

Channex pushes bookings to a webhook rather than us polling for them. The webhook is registered automatically during onboarding (when channex.webhook.base-url is set).

Webhook Endpoint

POST /api/channex/webhook?channelId=chnl_X

This path is public (permitted in SecurityConfig) because Channex calls it without our JWT. The channelId query parameter is embedded in the callback URL at registration time so the handler can resolve the right Channex credentials.

The endpoint always returns 200 OK — Channex retries on non-2xx, so failures are logged and persisted for retry rather than surfaced as errors.

Ingestion Flow

ChannexBookingService.handleWebhook runs on each call:

1. Ignore non-booking events (only booking / booking_new /
booking_modification / booking_cancellation are processed).
2. Extract booking_id, revision_id, property_id from the payload.
3. Resolve our listingId from channex_property (by channex_property_id).
4. Fetch the full booking document → GET /bookings/{id}
5. Persist it raw (idempotent on channex_booking_id) as RECEIVED.
6. Acknowledge it → POST /bookings/{id}/ack
and flip the row to ACKED.

If the full fetch fails, the raw webhook body is stored instead so nothing is lost. If credentials are missing, the booking is stored but left un-acked.

Inspect Ingested Bookings

GET /api/v1/admin/channex/bookings?limit=50

Returns recent channex_booking rows (capped at 200), newest first, each with its status, event, payload, and acknowledgedAt.

Scope

The integration persists and acknowledges bookings; converting a Channex booking into the internal Order / Cart domain is intentionally out of scope. The raw payload is retained so that conversion can be added later without data loss.