Setting up the unification of client data from different 1C-Bitrix channels

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
    1212
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815
  • 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
    565
  • 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
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    980

Setting up customer data consolidation from different channels in 1C-Bitrix

One person leaves a trace in five places simultaneously: orders on the site, writes to Bitrix24 chat, calls through telephony, makes a purchase at checkout and subscribes to email list. In each channel — a separate identifier. Linking them into one profile without losses is a technical task that the standard sale module doesn't solve out of the box.

Data entry points and their storage locations

In a typical Bitrix installation, customer data is scattered across several independent tables:

  • Website: b_user (registered), b_sale_fuser (anonymous), b_sale_order_props_value (contacts in order)
  • Bitrix24 CRM: b_crm_contact, b_crm_contact_phone, b_crm_contact_email — separate tables for multi-value fields
  • Email lists: b_subscribe_subscriber linked to USER_ID or just to an address without authorization
  • Telephony: b_voximplant_call with CALLER_ID field — phone number without link to b_user
  • Offline checkout: data comes via 1C exchange into b_iblock_element (if catalog is used) or into external table via custom module

Matching by identification keys

Consolidation is built on a chain of deterministic keys. Main ones: email, normalized phone number, cookie BITRIX_SM_SALE_UID (this is FUSER_ID), mobile device identifier from b_push_sender_subscription.

Phone normalization is a mandatory step. In b_crm_contact_phone numbers are stored as +7XXXXXXXXXX, in b_sale_order_props_value — in arbitrary format ("8 (495) 123-45-67"). For matching you need a function to convert to E.164:

function normalizePhone(string $phone): string {
    $digits = preg_replace('/\D/', '', $phone);
    if (strlen($digits) === 11 && $digits[0] === '8') {
        $digits[0] = '7';
    }
    return '+' . $digits;
}

Identifier graph

An efficient consolidation architecture is a matching table (identity graph). At database level this is a separate table, for example bl_customer_identity:

CREATE TABLE bl_customer_identity (
    id          SERIAL PRIMARY KEY,
    master_uid  INT NOT NULL,          -- ID of master profile in b_user
    channel     VARCHAR(50) NOT NULL,  -- 'web', 'crm', 'voip', 'pos', 'email'
    ext_id      VARCHAR(255) NOT NULL, -- identifier in channel
    created_at  TIMESTAMP DEFAULT NOW(),
    UNIQUE (channel, ext_id)
);

On each event in any channel (new order, call, subscription) the system searches for a record by (channel, ext_id). If found — links to existing master_uid. If not — checks for overlaps through other keys via b_crm_contact_phone and b_user.EMAIL, and only in complete absence creates a new master profile.

CRM module integration

Bitrix24 provides an API for finding duplicates: CCrmContact::GetList() with phone filter. The CCrmContactHelper::FindDuplicate() method searches for matches among contacts and leads. However, it only works within CRM and doesn't affect b_user.

For bidirectional linking: b_crm_contact has a field UF_CRM_WEB_USER_ID (custom field, needs to be created via CUserTypeEntity::Add()). Fill it on each successful match — and reverse search "contact → website user" becomes O(1) by index.

Conflict handling during merge

The most painful scenario: two profiles with different emails but one phone — and both with order history. You need a strategy for choosing the master record. A working approach: master = profile with the highest order sum in b_sale_order. All other records are aliases, their data is transferred to master, and the accounts are deactivated (ACTIVE = 'N' in b_user).

What we configure

  • Audit of existing channels and data entry points in the system
  • Creation of bl_customer_identity table and logic for its population
  • Function for phone and email normalization across all channels
  • Event handlers: OnSaleOrderSaved, OnCrmContactAdd, OnVoximplantCallEnd
  • API endpoint for mobile app passing device ID on authorization
  • Administrative interface for manual conflict resolution during merge