Setting up unified customer profile in 1C-Bitrix
A customer places an order on the site under one email, then calls the call center and provides a different phone number, then logs in from a mobile app as a guest. As a result, there are three different "customers" in the database, and the manager doesn't see the full history. This is a classic profile duplication problem that is solved by a unified customer profile — Customer Data Profile — combined with the sale module and Bitrix24 CRM.
How user data is stored in Bitrix
The core operates with two independent entities: user (b_user) and buyer (b_sale_person_type + b_sale_order_user). When placing an anonymous order, a record is created in b_sale_order with USER_ID = 0 and contact details in the b_sale_order_props_value fields. When registering or logging in, orders are linked to a specific USER_ID, but the connection "guest orders → registered user" is not built automatically.
Additionally, if the crm module is connected to the project, each order via the OnSaleOrderSaved event creates or updates the CCrmContact and CCrmDeal entities. Duplication at the b_user level immediately creates duplicate contacts in CRM.
Profile merge mechanism
The unified profile is built through three components:
1. Identification by deterministic fields. Email and phone are the main merge keys. In the b_user table, the EMAIL and PERSONAL_PHONE fields should be unique (index UQ_USER_EMAIL). The problem is that by default Bitrix does not prevent two users from having the same phone if it is stored in user properties via b_user_field.
2. Merge via user API. When a duplicate is detected, CUser::Merge() is called — the method transfers all orders, subscriptions, bonus points to the master account and deactivates the duplicate. It is important to first check dependent tables: b_sale_order, b_sale_fuser, b_rating_vote, b_forum_user, b_subscribe_subscriber.
// Merge: all duplicate data goes to master user
$result = CUser::Merge($masterUserId, $duplicateUserId);
if (!$result) {
// Log the reason — usually a UNIQUE field conflict in b_user
$GLOBALS['APPLICATION']->GetException();
}
3. Table b_sale_fuser (fake user). This is a key table for anonymous sessions. Each guest receives a record in b_sale_fuser with USER_ID = NULL. On login, the method CSaleUser::DoAutoLogin() should link FUSER_ID to real USER_ID. If this step is skipped — the cart and incomplete orders remain "suspended" and don't make it to the profile.
User fields as a source of problems
Additional customer attributes (date of birth, gender, preferences) are stored in b_uts_user — automatically created table for user fields (UserTypeEntity with ENTITY_ID = 'USER'). When merging via CUser::Merge() this data is not transferred automatically — the method only copies fields from the main b_user table. You need to manually transfer the values from b_uts_user before calling the merge.
Binding guest actions after authorization
The standard handler OnAfterUserAuthorize fires on each login. It's convenient to implement in it:
AddEventHandler('main', 'OnAfterUserAuthorize', function($fields) {
if ($fields['USER_ID'] > 0) {
// Transfer guest's cart to authorized user
$fuserId = CSaleUser::GetAnonymousUserID();
CSaleBasket::TransferBasket($fuserId, $fields['USER_ID']);
// Link view history (b_catalog_viewed)
// Transfer deferred items (b_sale_basket with LID = 'Y')
}
});
What we configure
- Audit of the
b_usertable for duplicates by email and phone by searching viaGROUP BY+HAVING COUNT(*) > 1 - Deduplication script with master account prioritization (by registration date or number of orders)
- Data transfer from
b_uts_user,b_sale_order,b_sale_fuserduring merge -
OnAfterUserAuthorizehandler for linking guest actions to profile - Configuration of unique indexes on phone if it is stored in
b_user_field - CRM integration: deduplication of
CCrmContactbyPHONEviaCCrmContactHelper::FindDuplicate()







