1C-Bitrix Integration with Bitrix24 CRM
A common pain point: an online store on 1C-Bitrix takes orders while the sales team works in Bitrix24 CRM. Managers manually transfer data between systems — orders are lost, contacts duplicated, and the customer's history is invisible. The solution is REST API + webhooks between two products from the same vendor, but configuring it correctly is harder than it appears.
Integration Architecture
Bitrix24 provides a REST API via OAuth 2.0 or incoming webhooks. 1C-Bitrix communicates with it through the bitrix24connector module (if installed) or directly via \Bitrix\Main\Web\HttpClient.
Key REST methods used in the integration:
| Bitrix24 method | Purpose |
|---|---|
crm.lead.add |
Create a lead from a site form |
crm.contact.add / crm.contact.update |
Create/update a contact |
crm.deal.add / crm.deal.update |
Create/update a deal from an order |
crm.deal.productrows.set |
Attach product line items to a deal |
crm.company.add |
Create a company (for B2B) |
crm.activity.add |
Add an activity (call, email) |
Two-Way Synchronization: Where Conflicts Arise
One-way transfer (site → CRM) is straightforward to implement. Problems begin with two-way sync: a manager changes a deal stage in Bitrix24 → the site should update the order status. At the same time, a customer on the site may modify an order → CRM must update the deal.
Scenario. A building materials online store, 200–300 orders per day. Three days after launching two-way sync, order statuses started drifting — an order paid on the site was reverting to "new" from CRM. The cause: the webhook from Bitrix24 arrived after the site event and overwrote the status without checking priority.
Solution — a source-of-truth field (source_system) in the status mapping:
// When receiving a webhook from B24 — check the timestamp
$localOrder = CSaleOrder::GetByID($orderId);
$localUpdated = strtotime($localOrder['DATE_UPDATE']);
$b24Updated = strtotime($webhook['data']['FIELDS']['DATE_MODIFY']);
if ($b24Updated > $localUpdated) {
// Update the order on the site
CSaleOrder::StatusOrder($orderId, $newStatus);
}
Status Mapping
Order statuses in 1C-Bitrix are stored in b_sale_status; deal stages in Bitrix24 are stored in pipeline stages (crm.status.list). The mapping is not one-to-one: Bitrix24 may have 10 stages while the site has 5 statuses. A correspondence table is created and stored in a custom table or module settings.
| Order status (site) | Deal stage (B24) |
|---|---|
| N (new) | NEW |
| P (paid) | WON |
| F (delivered) | WON |
| C (cancelled) | LOSE |
| D (in delivery) | EXECUTING |
Product Catalog Synchronization
If Bitrix24 uses a product catalog (crm.product.*), it must be kept in sync with the site catalog. Otherwise deals will contain manually-entered line items without a link to the product catalog. The method crm.deal.productrows.set accepts an array of line items with PRODUCT_ID — the product ID in the Bitrix24 catalog.
Strategy: when a product is created on the site via the OnAfterIBlockElementAdd event, automatically create/update the record in Bitrix24 via crm.product.add. XML_ID is stored as an external identifier for deduplication.
Error Handling and Queuing
The Bitrix24 REST API has a rate limit: 2 requests per second on cloud plans. A direct synchronous call during an order spike will hit this limit. The correct architecture uses a queue:
- A site event (
OnSaleOrderSaved) places a task in the queue table. - A Bitrix agent processes the queue every 30 seconds at a rate of 2 requests/second.
- On error, the record remains in the queue with an incremented attempt counter (max 5).
| Integration phase | Effort |
|---|---|
| OAuth / webhook configuration | 2–4 h |
| Field and status mapping | 4–6 h |
| Event handler development | 8–12 h |
| Queue implementation and error handling | 6–10 h |
| Two-way status synchronization | 6–10 h |
| Testing and debugging | 8–12 h |







