The brand-channel checkout proxy lets internal clients drive the full purchase flow on booking.hofj.com, weebora.com, and terrarossa.com through a single, normalized HoJ API surface. The brand is selected at every call with ?brand=; the resolved domain is both the upstream host and the tenant routing value.
These routes are internal only. The calling client must have profile=internal and allowedEntities includes itineraries (cart manipulation), bookings (confirmation + user-scoped lookup), and trips (user-scoped lists). Unknown brands are rejected against the CMS distribution-channels list (see GET /v1/distribution-channels).

How it works

Every response uses the standard HoJ envelope:
meta.now is forwarded from the brand site (epoch ms) for clock-skew accounting on the frontend. Errors are RFC 7807 problem-details (application/problem+json).

Authentication

  • Internal client credentials: standard HoJ Bearer (API key or OAuth2 client_credentials). Sent on every call as Authorization: Bearer <key>. Required for all routes.
  • End-user Bearer (X-End-User-Authorization): only required by user-scoped reads (GET /v1/bookings/:id, GET /v1/trips/:status, GET /v1/trips/active-summary). The proxy forwards it verbatim as the upstream Authorization header (the brand site expects the Cognito access token from the user’s session).
For staging, use the staging API base URL and a staging brand such as staging.weebora.com (or simply Weebora, resolved through /v1/distribution-channels).

1. Pre-flight — pick a product

The brand-site productId must match a product visible on the chosen brand. For internal flows you can either look it up from the public listing or via trip code:
POST /v1/itineraries accepts either form for productId:
  • numeric CMS id (e.g. 181) — matches the id field of GET /v1/products.
  • alphanumeric trip / provider code (e.g. t0054825) — matches providerID / tripCode on the internal DTO.

2. Create itinerary

Response:
Persist itineraryId — it identifies the cart on every subsequent call.

3. Experience options (activities)

4. Accommodation

5. Customer info

6. Pax details

GET .../pax returns one slot per traveler with a stable refId. PUT .../pax accepts the full array; each element MUST preserve its refId.

7. Payment

Optionally apply promo code / change currency, then create the Stripe payment intent. The brand site does not accept GET on this route (it returns 405). Use POST and send paymentType. Do not send "deposit" or "balance" as paymentType. Those are product names; the brand validator only accepts "full" or "plan".
GET /v1/itineraries/{id}/payment still exists as a compatibility alias: the API translates it to the brand POST and defaults paymentType to full (or honours ?paymentType= / ?planIndex=). New integrations should use POST. Response:
On the frontend, hand the data to Stripe.js:

8. Thank-you — confirm booking

After Stripe confirms the payment, confirm the reservation with the same paymentType (and planIndex when using a plan). If you omit paymentType, the API defaults to "full" before forwarding to the brand — a body of only { "itineraryId" } is enough for a full payment.
Response:
data is the human-readable reservation code — display it on the thank-you page.

Post-purchase lookups (user-scoped)

These routes are user-scoped on the brand site. Pass the end-user’s Cognito access token in X-End-User-Authorization:

Worked example — Magnifico Padel a Lanzarote

Product page: weebora.com/it/destinazioni/lanzarote/tocahub-lanzarote/magnifico-padel-a-lanzarote

Error model

All errors are application/problem+json per RFC 7807:

Operational notes

  • Idempotency: brand-site POST /booking is an upsert keyed by itineraryId — calling it twice after a successful payment is safe and returns the same reservation code.
  • Payment types: the brand validator only accepts paymentType "full" | "plan". Deposit / balance is plan + planIndex. The API defaults omitted paymentType to "full" on both payment and booking so a documented { "itineraryId" } body still works.
  • Timeouts: default 15s per upstream call (CHECKOUT_PROXY_TIMEOUT_MS). Activity / accommodation searches can be the slowest because they fan out to upstream availability.
  • Tenant routing: the same domain resolved from ?brand= is also sent as X-Nezasa-Channel to the CMS for content lookups. Use GET /v1/distribution-channels to see all registered brand names and domains.