1C-Bitrix Integration with Travelline Booking System
Travelline is a hotel management system (PMS) and booking engine widely used in the hospitality industry. A hotel website built on 1C-Bitrix must display real-time room availability, accept reservations, and transmit them to Travelline. Without a direct integration, the hotel either works via a Travelline widget iframe (inflexible UX) or manually transfers bookings from the website into the PMS.
Integration Architecture
Travelline exposes two APIs:
TL API v2 (JSON REST) — for retrieving rates, availability, and creating bookings. The primary API for website integration.
TL Distributor API — for large OTAs and aggregators; requires a separate agreement.
For a hotel website we use TL API v2. Endpoint: https://api.travelline.ru/api/v2/. Authentication via API key in the X-Api-Key header.
Travelline API Client
class TravellineApiClient
{
private string $apiKey;
private string $hotelId; // Property ID in TL
private string $baseUrl = 'https://api.travelline.ru/api/v2';
public function __construct(string $apiKey, string $hotelId)
{
$this->apiKey = $apiKey;
$this->hotelId = $hotelId;
}
/**
* Room availability
*/
public function getAvailability(string $arrivalDate, string $departureDate, int $adults = 2, int $children = 0): array
{
return $this->request('GET', '/availability', [
'hotelId' => $this->hotelId,
'arrivalDate' => $arrivalDate, // Y-m-d
'departureDate' => $departureDate,
'adults' => $adults,
'children' => $children,
]);
}
/**
* Rate plans and pricing
*/
public function getRatePlans(string $arrivalDate, string $departureDate): array
{
return $this->request('GET', '/rateplans', [
'hotelId' => $this->hotelId,
'arrivalDate' => $arrivalDate,
'departureDate' => $departureDate,
'currency' => 'RUB',
]);
}
/**
* Create a booking
*/
public function createBooking(array $bookingData): array
{
return $this->request('POST', '/bookings', array_merge(
$bookingData,
['hotelId' => $this->hotelId]
));
}
/**
* Cancel a booking
*/
public function cancelBooking(string $bookingId, string $reason = ''): array
{
return $this->request('POST', "/bookings/{$bookingId}/cancel", [
'reason' => $reason,
]);
}
/**
* Retrieve a booking
*/
public function getBooking(string $bookingId): array
{
return $this->request('GET', "/bookings/{$bookingId}");
}
private function request(string $method, string $path, array $data = []): array
{
$url = $this->baseUrl . $path;
if ($method === 'GET' && $data) {
$url .= '?' . http_build_query($data);
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
"X-Api-Key: {$this->apiKey}",
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => in_array($method, ['POST', 'PUT', 'PATCH'])
? json_encode($data) : null,
CURLOPT_TIMEOUT => 15,
]);
$json = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
$errorData = json_decode($json, true) ?? [];
throw new \RuntimeException(
"Travelline API {$httpCode}: " . ($errorData['message'] ?? $json)
);
}
return json_decode($json, true) ?? [];
}
}
Availability Caching
Fetching room availability from Travelline on every page request is not acceptable — it is slow (200–500 ms) and puts load on the TL API. We cache it:
class RoomAvailabilityService
{
private TravellineApiClient $tl;
public function getAvailability(string $arrival, string $departure, int $adults): array
{
$cacheKey = "tl_avail_{$arrival}_{$departure}_{$adults}";
$cacheTtl = 300; // 5 minutes
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache($cacheTtl, $cacheKey, '/travelline/')) {
return $cache->getVars()['data'];
}
$data = $this->tl->getAvailability($arrival, $departure, $adults);
$cache->startDataCache();
$cache->endDataCache(['data' => $data]);
return $data;
}
}
When data changes (a booking received via webhook) — invalidate cache via \Bitrix\Main\Data\Cache::clearByTag().
Search and Booking Component
/local/components/local/hotel.booking/ — a multi-step component:
Step 1 — Search. A form with check-in/check-out dates and guest count. On submission — a list of available rooms with prices from the TL API.
Step 2 — Room and Rate Selection. Room cards with photos from the 1C-Bitrix infoblock + prices and cancellation terms from the TL API. Data is merged by ROOM_TYPE_ID.
public function getRoomsWithPrices(string $arrival, string $departure, int $adults): array
{
// Room data from the 1C-Bitrix infoblock (description, photos, amenities)
$rooms = $this->getRoomsFromIblock();
// Availability and pricing from Travelline
$availability = $this->availabilityService->getAvailability($arrival, $departure, $adults);
// Merge by TL roomTypeId
$tlRooms = array_column($availability['roomTypes'] ?? [], null, 'id');
foreach ($rooms as &$room) {
$tlRoomId = $room['PROPERTY_TL_ROOM_TYPE_ID_VALUE'] ?? null;
if ($tlRoomId && isset($tlRooms[$tlRoomId])) {
$room['TL_DATA'] = $tlRooms[$tlRoomId];
$room['AVAILABLE'] = ($tlRooms[$tlRoomId]['availableRooms'] ?? 0) > 0;
$room['MIN_PRICE'] = $this->getMinPrice($tlRooms[$tlRoomId]['ratePlans'] ?? []);
} else {
$room['AVAILABLE'] = false;
}
}
return array_filter($rooms, fn($r) => $r['AVAILABLE']);
}
Step 3 — Guest Details. Form fields: first name, last name, email, phone, comment. Pre-populated from the user profile for authenticated users.
Step 4 — Booking Creation.
public function createBooking(array $formData, string $ratePlanId, string $roomTypeId): array
{
$arrival = $_SESSION['booking']['arrival'];
$departure = $_SESSION['booking']['departure'];
$bookingPayload = [
'arrivalDate' => $arrival,
'departureDate' => $departure,
'roomTypeId' => $roomTypeId,
'ratePlanId' => $ratePlanId,
'adults' => (int)$_SESSION['booking']['adults'],
'children' => (int)($_SESSION['booking']['children'] ?? 0),
'guest' => [
'firstName' => $formData['first_name'],
'lastName' => $formData['last_name'],
'email' => $formData['email'],
'phone' => $formData['phone'],
'countryCode' => 'RU',
],
'comment' => $formData['comment'] ?? '',
'currency' => 'RUB',
'source' => 'website',
];
$booking = $this->tl->createBooking($bookingPayload);
// Save booking locally in 1C-Bitrix
$this->saveBookingLocally($booking, $formData);
// Send confirmation to guest
\CEvent::Send('HOTEL_BOOKING_CONFIRMED', SITE_ID, [
'EMAIL' => $formData['email'],
'GUEST_NAME' => $formData['first_name'],
'BOOKING_ID' => $booking['bookingId'],
'ARRIVAL_DATE' => date('d.m.Y', strtotime($arrival)),
'DEPARTURE_DATE' => date('d.m.Y', strtotime($departure)),
'ROOM_NAME' => $booking['roomType']['name'] ?? '',
'TOTAL_PRICE' => $booking['totalPrice'],
]);
return $booking;
}
Webhooks from Travelline
Travelline sends notifications when a booking status changes (confirmed, cancelled, modified):
// /local/api/travelline/webhook.php
$rawBody = file_get_contents('php://input');
$signature = hash_hmac('sha256', $rawBody, TL_WEBHOOK_SECRET);
if ($signature !== ($_SERVER['HTTP_X_TL_SIGNATURE'] ?? '')) {
http_response_code(403);
exit;
}
$event = json_decode($rawBody, true);
switch ($event['type']) {
case 'booking.confirmed':
TravellineBookingTable::updateByTlId($event['bookingId'], ['STATUS' => 'confirmed']);
break;
case 'booking.cancelled':
TravellineBookingTable::updateByTlId($event['bookingId'], ['STATUS' => 'cancelled']);
// Notify guest of cancellation
break;
case 'booking.modified':
// Update booking data
break;
}
http_response_code(200);
echo json_encode(['received' => true]);
On-site Payment
If a rate plan requires a prepayment — we integrate a payment gateway (YooKassa, Tinkoff). The prepayment amount is taken from $booking['prepaymentAmount']. After successful payment — booking confirmation via TL API POST /bookings/{id}/confirm.
Storing Bookings in 1C-Bitrix
class TravellineBookingTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName(): string { return 'local_tl_bookings'; }
public static function getMap(): array
{
return [
new \Bitrix\Main\ORM\Fields\IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
new \Bitrix\Main\ORM\Fields\StringField('TL_BOOKING_ID', ['required' => true]),
new \Bitrix\Main\ORM\Fields\IntegerField('USER_ID'),
new \Bitrix\Main\ORM\Fields\StringField('GUEST_EMAIL'),
new \Bitrix\Main\ORM\Fields\StringField('GUEST_PHONE'),
new \Bitrix\Main\ORM\Fields\DateField('ARRIVAL_DATE'),
new \Bitrix\Main\ORM\Fields\DateField('DEPARTURE_DATE'),
new \Bitrix\Main\ORM\Fields\StringField('ROOM_TYPE_ID'),
new \Bitrix\Main\ORM\Fields\FloatField('TOTAL_PRICE'),
new \Bitrix\Main\ORM\Fields\StringField('CURRENCY'),
new \Bitrix\Main\ORM\Fields\StringField('STATUS'),
new \Bitrix\Main\ORM\Fields\DatetimeField('CREATED_AT'),
];
}
}
Scope of Work
- PHP client for Travelline API v2
- Availability caching with webhook-based invalidation
- Search and booking component (4 steps)
- Merging data from the 1C-Bitrix infoblock and TL API
- Webhook handler with status updates
- Email notifications to guests on confirmation and cancellation
- Payment gateway integration for prepaid rate plans
Timeline: 5–8 weeks for the base integration. 8–14 weeks including prepayment, guest account portal, and booking history.







