Integrating 1C-Bitrix with the Travelline booking system

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1173
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    745
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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.