Skip to content

Channex PMS certification

Channex certifies a PMS before it issues production credentials. GuestCraft has rehearsed every certification scenario against the independent Channex simulator, through the same public API the web app uses. That rehearsal is not the certification. The certification itself is the owner’s action with Channex staff, described at the end of this page.

The staging prerequisite is met: a sandbox workspace connects to Channex staging (see Environments). Certification can be scheduled once the owner has a staging account and the setup below.

Channex runs two separate installations, each with its own accounts, API keys and data: production (https://app.channex.io/api/v1) and staging (https://staging.channex.io/api/v1). GuestCraft selects one per workspace (ADR 0050):

  • A sandbox workspace uses Channex staging for every Channex call: connection checks, discovery and mapping, booking intake, acknowledgements and webhooks, ARI publication and read-back, and Messages.
  • A production workspace uses Channex production.
  • The environment is fixed when the workspace is created (ADR 0049) and Channels shows it, before the key is entered too.
  • A key is sent only to the workspace’s environment, never to the other one. When Channex refuses it (401, 403 or 404), Channels says that the key is invalid, has no property access, or belongs to the other environment. The Channex API does not say which, so GuestCraft cannot either.
  • Webhooks are registered on the workspace’s Channex environment, and always call GuestCraft’s own public address back.
  • A sandbox workspace whose key was saved before environments could be selected holds a key of production. It fails closed (environment_mismatch): GuestCraft makes no Channex call with it, except removing its own webhooks and Messages installation when a property is unlinked. Channels, sync health and the property’s alerts say so. To continue, unlink every property, wait for the webhook removal, connect a staging key, then link and map again.

What GuestCraft cannot detect: which environment a refused key belongs to, and whether an accepted key belongs to an account kept for testing. Certify only from a sandbox workspace.

As described in docs.channex.io, “PMS Certification Tests”, read on 2026-10-31, and in the Channex analysis:

  • It is free and self-directed, on Channex’s staging environment (https://staging.channex.io), with a staging account and a property called “Test Property”.
  • Every scenario must run from the real PMS and its real user interface. Scripts and Postman collections are not accepted.
  • For each scenario, the PMS operator writes down the Channex task IDs that the calls produced and submits them through Channex’s certification form, together with the answers to the extra-notes questions.
  • A live screen-sharing session with Channex staff follows, in which some scenarios are repeated.
  • When the session passes, Channex issues production credentials. These also require an active Channex subscription.

GuestCraft does not create anything in Channex: it links to a property, room types and rate plans that already exist there. Create these in the Channex staging UI, on a staging account:

  • A Channex property named “Test Property”, in USD.
  • Room types Twin and Double, each for 2 guests.
  • Rate plans BAR and B&B on each room type, with inheritance off.

Then do the following in GuestCraft, in a sandbox workspace (an instance administrator creates it in Organizations; an organization owner adds one in its settings):

  1. In Channels, check that the environment reads Staging, then connect the key of the Channex staging user.
  2. Create the property in USD, with the room types Twin (8 units) and Double (5 units) for two guests. Add the rate plans BAR (meal plan none) and B&B (breakfast) on each room type, per room.
  3. In Channels, link the property. Map Twin and Double, then select Register or check webhook.
  4. Map the four rate plans. Then select Activate publication and realign for the whole horizon, after confirming the takeover.

The simulator rehearsal builds exactly this property, in a sandbox workspace on a simulated Channex staging: packages/server/test/integration/channex-certification.integration.test.ts. The scenarios run in Channex’s order on that property. Each test counts and reads the calls it caused, and a last test checks that no call reached production.

Run the rehearsal with Docker available (it starts a disposable PostgreSQL):

Terminal window
pnpm --filter @guestcraft/server certification -- all # or a scenario number, 1 to 13

Scenario N also runs scenarios 1 to N-1, because they share one property, as in a session. The command runs the same tests CI runs and writes reports/channex-certification/:

  • index.md: per scenario, the operator actions, the ARI calls and their task IDs;
  • one JSON file per scenario: what the operator did in GuestCraft, and every Channex call GuestCraft then made, with its URL and host, payload, status and the task IDs Channex answered.

The pack never contains an API key, a webhook secret or guest data: request headers are not read, Channex’s answers are not stored beyond task IDs, and api_key, webhook headers and booking guest values are replaced. A test fails if any of them, or a secret header name, appears. Generated IDs are numbered as <n> in the order they first appear, the same ID always by the same number, and the rehearsal runs on its own clock, so two runs of the same code write the same pack. Each scenario test also asserts the scenario’s complete Channex transcript (every call’s method, path and status, in order): any other call, failed call or call without an answer fails it. The pack is simulator evidence, not the certification: its task IDs are the simulator’s. Use it to review what GuestCraft will send, and share it with Channex only as an explanation of the design.

Test names are those of packages/server/test/integration/channex-certification.integration.test.ts unless another file is named. Channex writes its nights inclusively (“1–10 Nov”). In GuestCraft’s rate calendar the same edit covers 1 November to 11 November, because the end is exclusive.

No scenario uses occupancy-based rates: each one sets a single price per plan and night, which GuestCraft publishes as a per-room plan’s rate. Keep the four plans per room for the session. Occupancy-based rates are an extra-notes answer below, with their own simulator test and the checks to make in staging.

# Channex scenario Simulator test How to perform it in GuestCraft
1 Full sync: 500 days of availability, rates and restrictions in 2 calls scenario 1: a full sync sends 500 days of varied availability, rates and restrictions in 2 calls Enter varied prices and restrictions in Rates, then Channels → Activate publication and realign for the 500-night horizon (takeover confirmed). Manual full sync is limited to once per 24 hours.
2 Twin/BAR 22 Nov → 333 scenario 2: a single date of a single rate is one restrictions call Rates: select Twin/BAR on 22 Nov and set the price to 333.
3 Twin/BAR 21 Nov 333; Double/BAR 25 Nov 444; Double/B&B 29 Nov 456.23, in 1 call scenario 3: single dates of several rates edited together are one call Make the three edits in Rates within 45 seconds of each other.
4 Twin/BAR 1–10 Nov 241; Double/BAR 10–16 Nov 312.66; Double/B&B 1–20 Nov 111, in 1 call scenario 4: date ranges of several rates edited together are one call Three range edits in Rates within 45 seconds.
5 Min stay: Twin/BAR 23 Nov 3; Double/BAR 25 Nov 2; Double/B&B 15 Nov 5, in 1 call scenario 5: minimum stays edited together are one call, with both kinds of minimum stay Rates: set the minimum stay on arrival and the minimum stay through (both), within 45 seconds.
6 Stop sell: Twin/BAR 14 Nov; Double/BAR 16 Nov; Double/B&B 20 Nov, in 1 call scenario 6: stop sells edited together are one restrictions call, with the rooms' availability Rates: set stop sell on the three plan-nights within 6 seconds.
7 Multiple restrictions in 1 call scenario 7: several restrictions over ranges of several rates edited together are one call Rates: set closed to arrival, closed to departure, maximum stay and minimum stays on the four ranges within 45 seconds.
8 Half a year: Twin/BAR 1 Dec 2026–1 May 2027 at 432, CTA/CTD off, min stay 2; Double/BAR at 342, min stay 3, in 1 call scenario 8: half a year of two rates is one call of one date range per rate Two range edits in Rates: a window of at most 366 nights, ending on 2 May (exclusive).
9 Availability from PMS bookings: Twin 8 → 7 on 21 Nov; Double 1 → 0 on 25 Nov, in 1–2 calls scenario 9: front-desk bookings of single dates send their availability, and a sell-out closes New reservation for one Twin night on 21 Nov and the last Double room on 25 Nov. Four Double rooms are already sold that night.
10 Twin 10–16 Nov → 3; Double 17–24 Nov → 4, in 1–2 calls scenario 10: a front-desk booking of several rooms over date ranges sends its availability in one call One New reservation: a Twin room for 10–17 Nov (departure), Duplicate room four times, then Add room for a Double room for 17–25 Nov, and save. A reservation holds up to 20 rooms.
11 Booking receiving through booking_revisions (not GET /bookings): new, modified and cancelled, each acknowledged scenario 11: a new, modified and cancelled booking arrive through booking revisions, each acknowledged Create, modify and cancel a booking in Channex staging, for example with its test booking tools. It appears in Reservations. Channels → Drain now forces intake if the webhook is slow.
12 Rate limits: a 429 pauses, and the queue resumes within Channex’s limits scenario 12: a 429 pauses publication, which resumes after Retry-After within Channex's limits Not triggered from the UI. Channels health shows the pause and its deadline. Answer with the facts in “Extra notes” below.
13 Update logic: only changes are sent; no timer full sync scenario 13: only changes are sent, and the nightly check reads Channex without a full sync; scenario 13: a rate plan's metadata save changes nothing ARI publishes and sends nothing; and in ari-reconcile.integration.test.ts: the nightly drift check > finds the extranet's edits after 02:00 and republishes exactly those cells, once a night Re-saving an unchanged price sends nothing. Channels health shows the nightly check and the differences it found.
14 Extra notes: min stay through and arrival, unsupported restrictions, multiple room types and plans, card details, PCI Not one test. See the answers below. Card data: packages/channex-client/src/resources/resources.unit.test.ts > booking revisions > drops card data and the raw OTA message while parsing Answered in the form.

Related simulator coverage of the publisher’s own recovery, in packages/server/test/integration/ari-reconcile.integration.test.ts:

  • the nightly drift check > …: drift detection and repair, booking hold, read failures.
  • the provider lookup of a send whose outcome is unknown > …: lost responses settled from Channex’s values, other writers and late requests left to the bounded repair.
  • the hosted dispatch of drift checks > …: hosted dispatch.

Where GuestCraft differs from a scenario’s wording

Section titled “Where GuestCraft differs from a scenario’s wording”

The tests assert what GuestCraft does in these cases:

  • Full commercial state per changed cell. A restrictions call carries every changed plan-night with its complete state: rate, min_stay_arrival, min_stay_through, max_stay (0 means no maximum), closed_to_arrival, closed_to_departure and stop_sell. It does not carry only the field that was edited. Unchanged plan-nights are never sent.
  • “1 call” means one gathering window. Rate and restriction edits are gathered for 45 seconds per property, and availability and stop sell for 6 seconds. Edits made together fall into one call. Edits spaced further apart go out as separate calls. Each endpoint gets at most one call per 6 seconds per property.
  • Stop sell (scenario 6) is one restrictions call plus one availability call. The availability call carries the absolute availability of the touched room-nights, because a stop sell on every plan of a room closes the room.
  • Sell-out (scenario 9) sends availability 0 and also stop_sell: true on every mapped plan of that room and night (ADR 0034), which makes 2 calls. Channex allows 1–2 calls.
  • Minimum stay (scenario 5): GuestCraft keeps and publishes both kinds of minimum stay. Set both to the scenario’s value.
  • Dates: a single night is sent as date_from equal to date_to. Equal consecutive nights merge into one range, so scenario 8 is one value per rate plan.
  • Availability is always absolute. Channex lowers availability by itself for OTA bookings. After an import, GuestCraft sends the absolute value, never a difference.
  • Rate plan edits. A save that changes nothing GuestCraft publishes (the plan’s name, code, meal plan, sort order or cancellation terms) is stored, versioned and audited, and sends nothing. Creating or unarchiving a plan, or changing a derived plan’s derivation, republishes the plan over the whole horizon, and with it its room type’s availability, which is always absolute: avoid changing derivations during the live session.
  • The nightly check is a read. At 02:00 property time, GuestCraft reads Channex’s values over the verified horizon, in windows of 92 nights (12 reads for 500 nights). It then republishes only the cells that differ, through the ordinary publisher (ADR 0048). There is no timer-driven full sync.
  • Rate limits. GuestCraft makes at most one call every 6 seconds per property and endpoint, which is within Channex’s 10 per minute. A 429 or 5xx response pauses the property’s publication for at least one minute, or for the Retry-After period. The pause backs off exponentially. Changes made during the pause stay queued and are published afterwards with their current values. Rejected items back off from one minute to one hour. Automatic retries stop after six attempts, and the item is shown in Channels for an administrator.
  • Update logic. Every save emits a domain event and an outbox change in the same transaction (ari_changes). A worker publishes these through numbered leases, with database-held pacing, retries and gathering windows. Rates and restrictions are compared with the values Channex last verified, and only differences are sent. Availability is sent as absolute values for the affected nights. The manual full sync is limited to once per 24 hours, and the nightly drift check only reads.
  • Min stay through and arrival. Both are supported and published separately (min_stay_arrival, min_stay_through). The deprecated single min_stay is never sent.
  • Restrictions GuestCraft does not publish:
    • availability_offset and max_availability. Channel availability rules are read and shown read-only, never written.
  • max_sell. Published as the plan’s remaining count (cap minus every sale GuestCraft knows, direct ones included), null for no cap, and read back by the drift check. To confirm with Channex staff: whether Channex counts the bookings it receives against max_sell (if so, OTA bookings are counted twice and the plan under-sells, never oversells), and how each channel applies it.
    • The days weekday filter. GuestCraft expands weekdays into dates.
    • Inherited values. Mapped plans must have inheritance off. Derived plans are computed in GuestCraft and sent as absolute rates.
  • Occupancy-based rates. Implemented: a per-person plan sends rates with every adult count’s price on each night, never rate, and never a partial list: a night with any count unpriced is sent as stop sell. Mapping requires a Channex per_person plan in manual rate mode with exactly one option per adult count, one main option, no derived option, children_fee/infant_fee equal to the plan’s age band fees, the property’s hotel policy (hotel_policy_id, GET /hotel_policies/{id}: infant_max_age, children_max_age) giving every age the bands’ category, and a curve the plan’s declared channels can represent (see ARI publication and the distribution matrix). The nightly drift check republishes the occupancies Channex’s read cannot return on a rotating seventh of the horizon. Tested against the simulator only (ari-reconcile.integration.test.ts, “a per-person plan”). To verify in staging:
    • which occupancy’s rate GET /restrictions returns for a per-person plan (GuestCraft assumes the main option’s; its drift repair and lookup depend on it);
    • that GET /hotel_policies/{id} answers for the key’s property with the ages the extranet shows, and that a property without a policy has hotel_policy_id null;
    • how Channex answers an occupancy that is not one of the plan’s options, a rates list on a per-room plan and a single rate on a per-person plan (the simulator refuses all three);
    • whether children_fee and infant_fee apply per night and per child (GuestCraft charges a band’s fee per night as a declared local rule), whether the channels apply the hotel policy’s infant_max_age/children_max_age inclusively as GuestCraft compares them, and whether an integer fee is in minor units;
    • how each connected OTA receives the per-occupancy prices and the fees (the distribution matrix lists what is verified).
  • Multiple room types and rate plans. Supported: any number of room types, and any number of plans per room type. Only mapped plans publish. Plans for direct sales only can stay unmapped.
  • Card details. GuestCraft never receives or stores card data. The Channex client drops a revision’s guarantee and the raw OTA message while parsing (test above), and GuestCraft does not call secure.channex.io.
  • PCI. GuestCraft is not PCI certified and does not ask for card access. Payments, if needed, go through Channex’s Stripe tokenization or Payments app.

Before the session:

  1. Create a Channex staging account (self sign-up on staging.channex.io), a Channex user for GuestCraft and its API key, and the Channex setup above. Never use the production key: it belongs to real client properties.
  2. In GuestCraft, set up the sandbox workspace and the Test Property as above. Run pnpm --filter @guestcraft/server certification -- all on the same GuestCraft version and read reports/channex-certification/index.md: it shows the calls each scenario makes.

During the session, from GuestCraft’s screens in the sandbox workspace, in this order:

  1. Scenarios 1 to 10 as in the table, each after the previous one’s calls are published. Channels → Publication health lists each send with its Channex task IDs (GET /api/v1/properties/{id}/channex/ari, sends[].tasks); write them down per scenario. Avoid changing derived plans’ derivations (see “Rate plan edits”).
  2. Scenario 11 with Channex’s test booking tools, then check the reservation in Reservations.
  3. Scenarios 12 and 13 are answered with the facts below; Channels health shows the nightly check.
  4. Not a Channex scenario: retire and replace a test plan and a test room type from Channels → Availability and rates sync, and confirm in Channex that the old target stays closed on every night (live target retirement is verified against the simulator only).

Send to Channex, through its certification form:

  • the task IDs of each scenario, from Channels;
  • the answers to the extra-notes questions below, and the scenarios that differ from the wording (“Where GuestCraft differs” above);
  • if staff ask how GuestCraft works, the rehearsal’s index.md and this page.

Then book and hold the live screen-sharing session, where staff watch the calls of some scenarios again from the same screens. Messages is a paid Channex app per property: its activation asks for the cost acknowledgement, and certification does not need it.

  1. The staging account and the session.
  2. After certification, production credentials and the subscription Channex requires.
  3. Written answers to the open questions below.
  • Does one certification of GuestCraft cover every installation of the open-source code, or must each installation or agency account certify? Can an agency serve several client hotels from its own account? Do local code changes invalidate a certification? (the Channex analysis)
  • Can rate limits be raised for large groups?
  • Are staging and production key sets guaranteed disjoint, and is there a documented error for a key presented to the other environment? GuestCraft shows only the generic refusal.
  • Are the Messages installation, thread, attachment and webhook APIs fully enabled on staging, with attachment URLs on staging.channex.io? The public OpenAPI omits parts of them.
  • Must the callback URL be reachable from both environments, and are webhook registrations isolated per environment when the same callback URL is used?
  • Which task or evidence fields do staff want for the live session, beyond task IDs?

The Booking Reporting calls (no-show, invalid card, cancellation over an invalid card; booking reports) are not certification scenarios and have run only against the simulator. They need a live check on a staging Booking.com/Expedia test booking: Channex’s actual windows (GuestCraft’s reading of the update window is an inference), the error code for a closed window, and the OTA names Channex sends.