Booking Hotels — Direct API & SSO Checkout
Once you've confirmed pricing and received a pricing token, there are two ways to complete a hotel booking through the Xeni API. This article covers both approaches.
Booking Options at a Glance
| Method | How It Works | Best For |
|---|---|---|
| Direct API Booking | Call the booking endpoint programmatically. Process payments through your own merchant account or through Xeni's payment processing. | Enterprise integrations, white-label platforms, custom checkout flows. |
| SSO Checkout | Redirect the guest to Xeni's hosted checkout page where they enter payment details and complete the booking. | Quick integrations where you don't want to handle payment processing. |
*
Option 1: Direct API Booking
The direct API approach gives you full control over the booking experience. You collect guest details, process payment through your own merchant account (or Xeni's), and call the Xeni booking endpoint to create the reservation.
Endpoint
POST /hotels/api/v2/bookings?pricingtoken={pricingtoken}
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
pricingtoken | string | Yes | The pricingtoken from the price confirmation response. Not the availability token. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
bookingid | string | Yes | A unique booking identifier generated by your system. |
rooms | array | Yes | Array of guest objects, one per room. |
rooms[].title | string | Yes | Guest title (e.g., "Mr", "Ms", "Mrs"). |
rooms[].firstname | string | Yes | Guest first name. |
rooms[].lastname | string | Yes | Guest last name. |
email | string | Yes | Guest email address for booking confirmation. |
phone | object | Yes | Guest phone number. |
phone.countrycode | string | Yes | Phone country code (e.g., "1" for US). |
phone.number | string | Yes | Phone number (digits only). |
Example Request
POST /hotels/api/v2/bookings?pricingtoken=eyJhbGciOiJIUzI1NiJ9.pricingfinal...Authorization: {signature}x-correlation-id: {correlation_id}Content-Type: application/json
{ "bookingid": "XENI-1700000000-ABC123DEF", "rooms": [ { "title": "Mr", "firstname": "John", "lastname": "Smith" } ], "email": "john.smith@example.com", "phone": { "countrycode": "1", "number": "5551234567" }}
Example Response
{
"data": {
"booking_id": "XENI-1700000000-ABC123DEF",
"confirmation_number": "HTL-98765",
"booking_status": "confirmed"
}
}Payment Processing
When using the direct API approach, you have two options for handling payments:
- Your own merchant account — Process the payment through your existing payment processor (Stripe, Braintree, Adyen, etc.) before or after calling the Xeni booking endpoint. This gives you full control over the payment experience, fees, and reconciliation.
- Xeni's payment processing — Use Xeni's merchant to handle payment collection. Contact your Xeni account representative for details on enabling this option and the available payment methods.
Recommended for enterprise: Most enterprise clients use the direct API booking with their own payment processor. This gives you complete control over the checkout UX, payment methods, and financial reconciliation.
*
Option 2: SSO Checkout (Hosted Page)
If you prefer not to handle payment processing, Xeni provides a hosted checkout page. You generate a URL with the booking parameters and redirect the guest to it. Xeni handles payment collection and booking confirmation on the hosted page.
Benefit: SSO checkout eliminates the need for your application to handle sensitive payment data. All payment processing is handled by Xeni's PCI-compliant checkout infrastructure.
Checkout Environments
| Environment | Checkout Base URL |
|---|---|
| UAT (Sandbox) | https://lifestyle2.uat.booking.clubxeni.com |
| Production | https://lifestyle2.booking.clubxeni.com |
Checkout URL Structure
The checkout URL follows this pattern:
{checkoutbaseurl}/booknow/hotels/v2/checkout?{parameters}
Required Parameters
All parameters are passed as URL query string values. Objects and arrays must be JSON-stringified and URL-encoded.
| Parameter | Type | Description |
|---|---|---|
correlationId | string | The correlation ID from the current search session. |
startDate | string | Check-in date in YYYY-MM-DD format. |
endDate | string | Check-out date in YYYY-MM-DD format. |
location | JSON string | Location object (see format below). |
occupancy | JSON string | Occupancy array (see format below). |
pricingToken | string | The pricingtoken from the price confirmation response. Not the availability token. |
propertyId | string | The hotel's propertyid. |
roomId | string | The room id from the availability response. |
stayPeriod | JSON string | Stay period object with start and end dates. |
nationality | JSON string | Guest nationality object. |
forceGet | string | Set to "true". |
isOTA | string | Set to "true" for OTA integrations. |
paging | JSON string | Paging configuration object. |
preference | JSON string | Set to "[]" (empty JSON array). |
*
Parameter Formats
Location Object
{
"id": "12345",
"name": "Miami",
"full_name": "Miami, Florida, United States",
"country": "United States",
"type": "City",
"location": {
"lat": 25.7617,
"long": -80.1918
}
}Use the location data returned by the autocomplete endpoint. The id must be a string.
Occupancy Array
[
{
"id": "room11700000000",
"numOfRoom": 1,
"adults": 2,
"childs": 0,
"childages": []
}
]Each element represents one room. The id field should be a unique identifier (any string).
Stay Period Object
{
"start": "2025-06-01",
"end": "2025-06-05"
}Nationality Object
{
"name": "United States",
"alpha2Code": "US"
}Paging Object
{
"pageNo": 1,
"pageSize": 50
}*
Full Example: Building the Checkout URL
JavaScript
function generateCheckoutUrl(params) { const baseUrl = 'https://lifestyle2.uat.booking.clubxeni.com';
const location = { id: String(params.location.id), name: params.location.name, fullname: params.location.fullname, country: params.location.country, type: params.location.type || 'City', location: { lat: params.location.lat, long: params.location.long } };
const occupancy = params.occupancy.map((occ, index) => ({ id: room${index + 1}${Date.now()}, numOfRoom: 1, adults: occ.adults, childs: occ.childs || 0, childages: occ.childages || [] }));
const urlParams = new URLSearchParams({ correlationId: params.correlationId, startDate: params.startDate, endDate: params.endDate, forceGet: 'true', isOTA: 'true', location: JSON.stringify(location), nationality: JSON.stringify({ name: 'United States', alpha2Code: 'US' }), occupancy: JSON.stringify(occupancy), paging: JSON.stringify({ pageNo: 1, pageSize: 50 }), preference: JSON.stringify([]), pricingToken: params.pricingToken, propertyId: params.propertyId, roomId: String(params.roomId), stayPeriod: JSON.stringify({ start: params.startDate, end: params.endDate }) });
return ${baseUrl}/booknow/hotels/v2/checkout?${urlParams.toString()};}
Python
import jsonfrom urllib.parse import urlencode
def generatecheckouturl(params): base_url = "https://lifestyle2.uat.booking.clubxeni.com"
location = { "id": str(params["location"]["id"]), "name": params["location"]["name"], "fullname": params["location"]["fullname"], "country": params["location"]["country"], "type": params["location"].get("type", "City"), "location": { "lat": params["location"]["lat"], "long": params["location"]["long"] } }
occupancy = [{ "id": f"room{i+1}{int(time.time())}", "numOfRoom": 1, "adults": occ["adults"], "childs": occ.get("childs", 0), "childages": occ.get("childages", []) } for i, occ in enumerate(params["occupancy"])]
query = urlencode({ "correlationId": params["correlationid"], "startDate": params["startdate"], "endDate": params["enddate"], "forceGet": "true", "isOTA": "true", "location": json.dumps(location), "nationality": json.dumps({"name": "United States", "alpha2Code": "US"}), "occupancy": json.dumps(occupancy), "paging": json.dumps({"pageNo": 1, "pageSize": 50}), "preference": json.dumps([]), "pricingToken": params["pricingtoken"], "propertyId": params["propertyid"], "roomId": str(params["roomid"]), "stayPeriod": json.dumps({"start": params["startdate"], "end": params["enddate"]}) })
return f"{base_url}/booknow/hotels/v2/checkout?{query}"
*
Presenting the Checkout Link
Once you've built the URL, present it to the guest. Common approaches:
- Button redirect — Display a "Complete Booking" button that opens the checkout URL in a new tab.
- Automatic redirect — Navigate the user directly to the checkout page.
- Embedded iframe — Embed the checkout page within your application (check with your Xeni representative for iframe compatibility).
Example: HTML Button
<a href="{checkouturl}" target="blank" rel="noopener noreferrer" style="display: inline-block; padding: 12px 24px; background-color: #2196F3; color: white; text-decoration: none; border-radius: 6px;"> Complete Booking</a>
*
Important Considerations
- Pricing token expiration: The pricing token is valid for 10 minutes. Generate the checkout URL promptly after price confirmation. If the guest delays, you may need to re-confirm the price.
- One-time use: Each pricing token can be used for a single booking. After checkout is completed (or the token expires), a new token must be obtained for any subsequent booking.
- Correlation ID format: The correlation ID must be passed as-is from the API response headers. Do not modify it.
- URL encoding: JSON objects in query parameters must be properly URL-encoded. Use
URLSearchParams(JavaScript) orurllib.parse.urlencode(Python) to handle encoding automatically.