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:
- New booking in YCLIENTS → lead/deal in Bitrix CRM (top priority)
- Client from Bitrix CRM → client card in YCLIENTS (bidirectional sync)
- Booking cancellation in YCLIENTS → deal update in Bitrix
- 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.







