Skip to main content

Channel Manager (Channex)

The Channex integration distributes our catalogue to OTAs (Booking.com, Airbnb, Expedia, …) through the Channex channel manager. Unlike the Staah and Livbnb integrations — which pull rates and inventory into our database — Channex is a push integration: we mirror our catalogue up into Channex, push availability and rates, and receive OTA bookings back as real PMS bookings.

It lives in the admin-api module under com.elivaas.pms.

Entity Mapping

Our entityChannex entity
Listing (lst_…)Property
Property (prop_…)Room Type (+ one primary Rate Plan)
Inventory (per property/date)Availability (ARI)
ChannelRate (per property/date)Restriction / rate (ARI)
Channex booking revisionA real PMS bookings row (OTA-sourced)

A single listing becomes one Channex property; each of the listing's properties becomes a room type with a primary rate plan underneath it.

What it does

  • Catalogue mapping — onboard a listing → property, room types, rate plans, and a booking webhook (idempotent).
  • Availability & rates — push ARI, event-driven and debounced off our inventory.changed / rate.changed events, range-compressed, netting cart holds, with an hourly drift correction.
  • Bookings — receive OTA bookings via webhook and a feed-poll backstop, and map each into a real PMS booking (inventory decremented, money reverse-derived).
  • Channel API — connect an OTA (e.g. Booking.com) programmatically: test connection, read its rooms/rates, map them, activate.
  • Airbnb — the OAuth-based Airbnb connection flow, per-listing mappings, settings and promotions.
  • Doctor / health check — verify the integration end-to-end (key → API → mappings → ARI read-back → feed).

How It Works

Onboard a listing (channelId=chnl_X)        ── ChannexSyncService.onboardListing
├─ Create/update Channex PROPERTY ← Listing → channex_property
├─ For each property: ROOM TYPE + RATE PLAN ← Property → channex_room_type
└─ Register a booking WEBHOOK (idempotent) → channex_property.channex_webhook_id

Inventory / rate change (the normal path)
inventory.changed / rate.changed (SNS) → SQS → ChannexAriEventConsumer
→ debounce → push only the touched cells (availability + restrictions)
+ hourly drift job: full ARI push for every onboarded listing

OTA booking
webhook (revision_id) ── and ── feed poll (backstop, every 15 min)
→ GET /booking_revisions/{id} → store raw + ACK the revision
→ ChannexOtaBookingService: create/cancel a real PMS booking, adjust inventory

Stored Mappings

Channex UUIDs are persisted so subsequent syncs update in place (migration 047-add-channex-tables.sql):

TableGrainHolds
channex_propertyone row per onboarded listingchannex_property_id, channex_webhook_id, status
channex_room_typeone row per propertychannex_room_type_id, channex_rate_plan_id, status
channex_bookingone row per inbound bookingchannex_revision_id, raw payload, status (RECEIVED/ACKED/FAILED)

Each mapping row carries a status (PENDING / SYNCED / FAILED) and last_error, so a partial sync is visible per entity.

Configuration

Credentials are stored per channel in the channels.channel_config JSONB column, so multiple Channex accounts and environments can coexist:

FieldMeaning
channexApiKeyAccount API key, sent as the user-api-key header
channexEnvironmentSTAGING (default) → staging.channex.io, PRODUCTIONsecure.channex.io
channexGroupIdChannex group UUID a created property/channel is associated with
channexWebhookSecretOptional shared secret for verifying inbound webhooks

Application properties (feed poll, SQS, debounce, drift, booking auto-apply) are covered in Deployment & Config.