Skip to main content
Work through this list after a successful Sandbox integration and before sending live guest traffic to Production. Tick every box; each item links to the page that explains it.
Sandbox only has hotels in 12 test cities. Passing tests there proves your integration logic, not Production inventory. Repeat the discovery, search, rooms and prebook smoke tests against Production with Production IDs before opening to guests.

1. Portal and environment

  • Production supplier credentials are added and the intended suppliers are enabled in the Production portal (Portal & Environments).
  • A Production API key is created and stored in a secret manager, never in source control, client code or logs.
  • Your Production backend uses https://api.unifystays.com. Sandbox and Production keys, caches, stored IDs and bookings are fully separate.
  • A key rotation is rehearsed: deploy the new key, confirm traffic, then disable the old key.
  • All API calls come from your backend. The room stream is opened server-side (browsers cannot send x-api-key with EventSource).
  • Owners or admins who will approve AWAITING_REVIEW bookings have portal access and know the process (Create Booking).
  • Autocomplete is debounced, starts at 3 characters, and sends place_id or hotel_id (never the id UI key). A hotel destination.id is sent as a number.
  • The guest form uses the iso_code from Nationalities, and the same nationality is sent in search and in rooms. You never rely on the IN default.
  • Autocomplete (48 hours) and nationalities (30 days) are cached per Caching Strategy; prices and offers are not.
  • Search uses pricing_mode: "instant" for guest pages and polls price updates every 2 seconds only while results are visible, stopping on COMPLETE or EXPIRED.
  • Cards render by price.status (live, recent, checking, no_price, unpriced). You never show 0 or a placeholder when amount is null, and you handle unknown statuses as “no price”.
  • Search amounts are divided by 100 and displayed with price.currency (not assumed USD).
  • An expired search_id (404) triggers a fresh search. Paging keeps the same inputs, and filter or sort changes restart without a cursor.

3. Rooms and prebook

  • Room search sends the same dates, child_ages and nationality as the search, and opens sse_stream_url immediately.
  • The stream reader renders offers as they arrive, stops at done, closes the connection, reconnects with Last-Event-ID, and starts a new room search on ROOM_STREAM_REPLAY_GAP or ROOM_STREAM_NOT_FOUND.
  • A failing or slow supplier (provider_error, provider_skipped, done.status = PARTIAL) does not discard the other offers. Only FAILED offers a retry.
  • Room cards are drawn from families and variants (Get Hotel Rooms), each row shows its own provider_room beds and maximum guests, and tmp_ and room IDs are never stored or used as booking keys.
  • Prices from offers and prebook are read as amount_minor / 10^currency_exponent; payable_at_property is shown separately as “pay at hotel”; commercial values are never added to a ledger.
  • Notices with must_display: true and the full cancellation policy are shown before the guest pays.
  • Prebook is called when checkout starts, with the hotel_id and booking_token from the same option. You handle all three decisions: READY_TO_BOOK, RECONFIRMATION_REQUIRED (guest must accept the new price) and NOT_BOOKABLE (back to rooms).
  • The checkout form is built from booking_requirements of the prebook response, with a countdown to offer.lifecycle.expires_at.

4. Booking safety

  • Every booking attempt has one unique Idempotency-Key, generated and saved before the first request, up to 128 characters.
  • Retrying after a timeout uses the same key and the identical body, and you call Find Booking by Idempotency-Key when you have no response. A new key is used only for a deliberate new attempt.
  • data.booking_id is saved immediately next to your order number.
  • Create Booking sends every prebook room_allocation_id exactly once, with guest counts matching each room’s occupancy (age 0 or 1 is INFANT), exactly one lead guest, and answers keyed by the prebook requirement_id values.
  • The traveler’s public IP is sent in X-Unifystays-End-User-IP.
  • You never show “confirmed” for PROCESSING or AWAITING_REVIEW, and never issue a voucher before CONFIRMED. AWAITING_REVIEW shows “room and price are not reserved”.
  • A FAILED booking tells the guest and restarts from rooms with a new key; RECONCILIATION_REQUIRED never triggers a second booking.
  • If you use HOLD, a timer confirms before hold_deadline_at, and payment is collected before Confirm Hold.

5. Tracking, cancellation and webhooks

  • Webhooks are registered on the Production environment with a public HTTPS URL, tested with webhook.test, and verified against the raw request body (Verify Webhook Signatures).
  • The receiver returns 2xx within 10 seconds, stores the event id to ignore repeats, ignores unknown event types and fields, and applies events by data.object.version.
  • Status polling remains as a fallback (wait next_poll_after_ms, stop when is_terminal).
  • The hotel confirmation number is shown when present; if it is null after CONFIRMED, the guest sees “to follow” and you pick it up from booking.updated or a later read.
  • Cancellation is offered only when available_actions contains CANCEL. It uses a fresh quote, shows the penalty (never UNKNOWN as zero), sends a new Idempotency-Key, and says “cancelled” only at CANCELLED.
  • You alert on webhook consecutive_failures and on EXHAUSTED deliveries, and know how to replay them.
  • The webhook signing secret is in a secret manager and secret rotation is rehearsed.

6. Errors, limits and operations

  • Your client branches on HTTP status and error.code (Errors & Retries), never on message text, and shows its own guest-friendly messages.
  • Expired booking_token, prebooking_id and quote errors return the guest to a recoverable step, not a dead end.
  • Retries use exponential backoff with jitter, honour Retry-After, and never retry a 4xx unchanged. 429 RATE_LIMITED is handled and RateLimit-Remaining is monitored.
  • You log X-Request-Id (or request_id) for every failed call and keep it in support tooling, with the booking_id and idempotency key.
  • Guest personal data is not written to client-side logs or unprotected storage.
  • The client ignores unknown fields and handles unknown enum values (Versioning).
  • You have a support contact path for CONTACT_SUPPORT bookings and for supplier setup errors (424).

7. Final production smoke test

  • Autocomplete, search, price polling, rooms, prebook for a real Production destination.
  • One real booking in a refundable offer with a cancellation window of more than 2 days, ending CONFIRMED, with the webhook received and the voucher rendered.
  • The same booking cancelled through quote and cancel, ending CANCELLED.
  • A deliberate duplicate request with the same Idempotency-Key returns the original booking.

You are live

Once live, enable additional supported suppliers in the Unifystays portal. Your application keeps the same API contract, search flow and booking flow.
Review the API Reference or the IDs and Tokens Cheat Sheet.