Integration of 1C-Bitrix with online booking services (YCLIENTS)

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
    1175
  • 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
    747
  • 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

Integrating 1C-Bitrix with Online Booking Services (YCLIENTS)

YCLIENTS is an industry-specific platform for online booking in service businesses: beauty salons, medical clinics, fitness centers, and auto repair shops. When a website runs on 1C-Bitrix and the appointment management system is in YCLIENTS, a classic data gap emerges: a client books through the YCLIENTS widget on the site, but their data never reaches the Bitrix CRM. Marketing has no visibility into which ad the client came from, leads are not created, and the interaction history is fragmented. The integration goal is to synchronize these two systems.

What We Integrate

YCLIENTS provides a REST API v2. Key entities: records (bookings), clients, services, staff, and bookforms (booking forms). For the Bitrix integration, the primary focus is records and clients.

Scenarios:

  1. New booking in YCLIENTS → lead/deal in Bitrix CRM (top priority)
  2. Client from Bitrix CRM → client card in YCLIENTS (bidirectional sync)
  3. Booking cancellation in YCLIENTS → deal update in Bitrix
  4. YCLIENTS service showcase on the Bitrix site — via widget or API

YCLIENTS API: Authentication

YCLIENTS uses a Bearer token obtained in exchange for login/password credentials. The token has a long lifespan (several months) and is stored in the module settings.

namespace Local\YClients;

class ApiClient
{
    private string $baseUrl = 'https://api.yclients.com/api/v1';
    private string $partnerToken; // YCLIENTS partner token
    private string $userToken;    // specific user token

    public function __construct()
    {
        $this->partnerToken = \Bitrix\Main\Config\Option::get('local.yclients', 'partner_token');
        $this->userToken    = \Bitrix\Main\Config\Option::get('local.yclients', 'user_token');
    }

    public function request(string $method, string $path, array $data = []): array
    {
        $url = $this->baseUrl . $path;
        $ch  = curl_init();

        curl_setopt_array($ch, [
            CURLOPT_URL            => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 15,
            CURLOPT_HTTPHEADER     => [
                'Authorization: Bearer ' . $this->partnerToken . ', User ' . $this->userToken,
                'Accept: application/vnd.yclients.v2+json',
                'Content-Type: application/json',
            ],
        ]);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        } elseif ($method === 'PUT') {
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }

        $response = json_decode(curl_exec($ch), true);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode >= 400) {
            throw new \RuntimeException("YCLIENTS API error {$httpCode}: " . ($response['meta']['message'] ?? 'unknown'));
        }

        return $response['data'] ?? $response;
    }
}

Webhook: New Booking → Lead in Bitrix

YCLIENTS supports webhooks for events: record_created, record_updated, record_cancelled, client_created. Configuration is in the YCLIENTS account: Integrations → Webhook.

// /local/api/yclients-webhook.php
require_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');

$payload = json_decode(file_get_contents('php://input'), true);

// Verification: YCLIENTS sends X-Yclients-Hook-Signature
$signature = $_SERVER['HTTP_X_YCLIENTS_HOOK_SIGNATURE'] ?? '';
$body      = file_get_contents('php://input');
$secret    = \Bitrix\Main\Config\Option::get('local.yclients', 'webhook_secret');

if (hash_hmac('sha256', $body, $secret) !== $signature) {
    http_response_code(403);
    exit;
}

$eventType = $payload['resource'] ?? '';
$data      = $payload['data'] ?? [];

match ($eventType) {
    'record_created'   => (new \Local\YClients\RecordHandler())->onCreate($data),
    'record_cancelled' => (new \Local\YClients\RecordHandler())->onCancel($data),
    'record_updated'   => (new \Local\YClients\RecordHandler())->onUpdate($data),
    default            => null,
};

http_response_code(200);

Record handler:

namespace Local\YClients;

class RecordHandler
{
    public function onCreate(array $record): void
    {
        $client = $record['client'] ?? [];
        $phone  = $this->normalizePhone($client['phone'] ?? '');

        $services = array_map(
            fn($s) => $s['title'],
            $record['services'] ?? []
        );

        $staffName = $record['staff']['name'] ?? '—';
        $datetime  = $record['date'] ?? '';

        // Look for an existing contact/lead by phone number
        $existingLeadId = $this->findLeadByPhone($phone);

        $fields = [
            'TITLE'                => 'YCLIENTS booking: ' . implode(', ', $services),
            'PHONE'                => [['VALUE' => $phone, 'VALUE_TYPE' => 'WORK']],
            'NAME'                 => $client['name'] ?? '',
            'SOURCE_ID'            => 'OTHER',
            'STATUS_ID'            => 'NEW',
            'COMMENTS'             => sprintf(
                "YCLIENTS booking #%d\nService: %s\nStaff: %s\nDate: %s",
                $record['id'],
                implode(', ', $services),
                $staffName,
                $datetime
            ),
            'UF_YCLIENTS_RECORD_ID' => (string)$record['id'],
            'UF_YCLIENTS_DATE'      => $datetime,
        ];

        $lead = new \CCrmLead(false);

        if ($existingLeadId) {
            $lead->Update($existingLeadId, ['COMMENTS' => $fields['COMMENTS']], true);
        } else {
            $lead->Add($fields, true);
        }
    }

    public function onCancel(array $record): void
    {
        $ycId = (string)$record['id'];

        // Find lead by UF_YCLIENTS_RECORD_ID
        $res = \CCrmLead::GetList(
            [],
            ['UF_YCLIENTS_RECORD_ID' => $ycId],
            false,
            ['nTopCount' => 1],
            ['ID']
        );

        if ($row = $res->Fetch()) {
            $lead = new \CCrmLead(false);
            $lead->Update($row['ID'], ['STATUS_ID' => 'JUNK', 'COMMENTS' => 'Cancelled by client in YCLIENTS'], true);
        }
    }
}

Syncing Clients from Bitrix to YCLIENTS

When a deal is created in Bitrix (if the client is interested in a service), we automatically create or update the client in YCLIENTS:

namespace Local\YClients;

class ClientSync
{
    private ApiClient $api;
    private int $companyId; // company ID in YCLIENTS

    public function syncFromBitrix(int $bxLeadId): ?int
    {
        $lead  = \CCrmLead::GetByID($bxLeadId);
        $phone = $this->getLeadPhone($bxLeadId);

        if (!$phone) return null;

        // Look for the client in YCLIENTS by phone number
        $existing = $this->api->request('GET',
            "/clients/{$this->companyId}?phone=" . urlencode($phone)
        );

        $clientData = [
            'name'    => trim($lead['NAME'] . ' ' . $lead['LAST_NAME']),
            'phone'   => $phone,
            'email'   => $lead['EMAIL'] ?? '',
            'comment' => 'Source: Bitrix CRM, lead #' . $bxLeadId,
        ];

        if (!empty($existing)) {
            // Update existing
            $ycClientId = $existing[0]['id'];
            $this->api->request('PUT', "/client/{$this->companyId}/{$ycClientId}", $clientData);
        } else {
            // Create new
            $result     = $this->api->request('POST', "/clients/{$this->companyId}", $clientData);
            $ycClientId = $result['id'];
        }

        // Save the YCLIENTS client ID to the lead custom field
        $lead = new \CCrmLead(false);
        $lead->Update($bxLeadId, ['UF_YCLIENTS_CLIENT_ID' => $ycClientId], true);

        return $ycClientId;
    }
}

YCLIENTS Widget on the Bitrix Site

The simplest way to embed a booking form is to use the standard YCLIENTS widget. It is inserted via a Bitrix component at the appropriate location in the template.

Customization: the YCLIENTS widget supports parameters via its JS API — it is possible to pre-fill the selected service or staff member if the user navigated from a Bitrix product/service page.

Custom Fields Reference

Code Entity Purpose
UF_YCLIENTS_RECORD_ID Lead Booking ID in YCLIENTS
UF_YCLIENTS_CLIENT_ID Lead, Contact Client ID in YCLIENTS
UF_YCLIENTS_DATE Lead Booking date/time
UF_YCLIENTS_STAFF Lead Staff member name
UF_YCLIENTS_SERVICE Lead Service name

Scope of Work

  • YCLIENTS API setup: obtaining partner token and user token
  • Webhook endpoint with signature verification
  • Creating/updating leads from incoming bookings
  • Bitrix → YCLIENTS client synchronization
  • Creating custom fields in CRM
  • (Optional) Embedding the YCLIENTS widget into Bitrix component templates

Timeline: basic integration (webhook → lead) — 1–2 weeks. Bidirectional client sync and service showcase — 3–5 weeks.