We often encounter this situation: a retail chain keeps records in 1C:Retail, while the online store runs on 1C-Bitrix. A customer buys a product in a physical store—cashback should instantly appear in their personal account on the website. And vice versa: cashback spent online must be accounted for at the register on the next visit. Without balance synchronization, the loyalty program works separately for each channel, leading to errors and customer dissatisfaction.
Typical problems: duplicate accruals, lost operations due to connection drops, balance desynchronization. Without a reliable integration, the loyalty program becomes a source of errors, not a way to retain customers. We offer a turnkey two-way cashback synchronization architecture—from design to monitoring. Over the years, we have delivered 30+ cashback integrations for retail chains, with over 8 years of experience in 1C and Bitrix integration. In this article we will cover technical details: how to choose a master system, organize transaction idempotency (10x more reliable than simple CSV exchange), and handle offline operations. We will also discuss API contracts, user search, and synchronization monitoring. The investment starts from $4,500 for the API development and testing, and can save up to $20,000 annually in error correction.
Choosing the master system for cashback synchronization
The first option is to use 1C as the master system. In this case, Bitrix caches the balance, which simplifies consistency, but if 1C is unavailable, online debit becomes impossible. The second option is Bitrix as the master system. Online debit does not depend on 1C, but the offline register cannot debit cashback without a network. The third option is synchronous exchange with a queue. It is the most reliable, works in any mode, but requires a conflict resolution mechanism for simultaneous operations. For most projects, the first option with balance caching on the Bitrix side is optimal: it is twice as reliable as the second in typical register scenarios.
API on the Bitrix side
Create a REST endpoint in Bitrix for receiving and sending operations from 1C:
// /local/api/cashback/v1/
// Routing via urlrewrite.php or a separate file
class CashbackApiController
{
/**
* GET /local/api/cashback/v1/balance?user_phone=79001234567
* Used by 1C to check balance at the register
*/
public function getBalance(): void
{
$this->requireApiKey();
$phone = $_GET['user_phone'] ?? '';
$userId = $this->getUserIdByPhone($phone);
if (!$userId) {
$this->respond(['error' => 'user_not_found'], 404);
return;
}
$balance = CashbackBalanceTable::getBalance($userId);
$this->respond([
'user_id' => $userId,
'balance' => $balance,
'updated_at' => CashbackBalanceTable::getLastUpdated($userId),
]);
}
/**
* POST /local/api/cashback/v1/transactions
* 1С sends operations (accrual/debit for offline purchases)
*/
public function addTransaction(): void
{
$this->requireApiKey();
$body = json_decode(file_get_contents('php://input'), true);
$this->validateTransaction($body); // type, amount, external_id, user_phone
// Idempotency: external_id is unique on the 1C side
if (CashbackTransactionTable::existsByExternalId($body['external_id'])) {
$this->respond(['status' => 'already_exists', 'idempotent' => true]);
return;
}
$userId = $this->getUserIdByPhone($body['user_phone']);
\Bitrix\Main\Application::getConnection()->startTransaction();
try {
CashbackTransactionTable::add([
'USER_ID' => $userId,
'TYPE' => $body['type'], // accrual|debit
'AMOUNT' => $body['amount'],
'DESCRIPTION' => $body['description'] ?? '',
'EXTERNAL_ID' => $body['external_id'], // Document ID in 1C
'SOURCE' => '1c_retail',
'CREATED_AT' => new \Bitrix\Main\Type\DateTime($body['created_at']),
]);
if ($body['type'] === 'accrual') {
CashbackBalanceTable::credit($userId, $body['amount']);
} else {
CashbackBalanceTable::debit($userId, $body['amount']);
}
\Bitrix\Main\Application::getConnection()->commitTransaction();
$this->respond(['status' => 'ok']);
} catch (\Exception $e) {
\Bitrix\Main\Application::getConnection()->rollbackTransaction();
$this->respond(['error' => $e->getMessage()], 500);
}
}
}
Why idempotency is critical
The EXTERNAL_ID field in the transaction table is a unique identifier for the document in 1C. 1C forms it as {OperationType}_{DocumentNumber}_{Date}. When the same document is resent (network failure, retry), Bitrix responds with already_exists without double accrual—this protects against duplicate balances. This property is critical for correct loyalty program operation. As recommended by the 1C-Bitrix documentation, idempotency should be implemented at the business logic level of transaction processing.
Handling insufficient balance conflict: When synchronizing offline operations, Bitrix returns an error with code insufficient_balance. 1C should handle this: cancel the discount or request additional payment. The event is logged.
Implementation steps
- Analyze the current architecture: determine the master system, exchange channels, operation types.
- Design REST API on the Bitrix side: endpoints, contracts, idempotency.
- Develop an external handler for 1C: request formation, response processing.
- Test in an isolated environment: simulate offline mode, check conflicts.
- Deploy to production: configure monitoring, logging, notifications.
After that—warranty support after launch.
Handler on the 1C side
In 1C:Retail or 1C:Trade Management, an external handler or extension is created that:
- On cash receipt accrual—sends POST to Bitrix API
- On cashback debit at the register—first requests balance (
GET /balance), then POST withdebitoperation - On receipt cancellation—sends a
releaseoperation (cancel debit) or negative accrual
Example HTTP request from 1C (built-in HTTP client):
Запрос = Новый HTTPЗапрос("/local/api/cashback/v1/transactions");
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Запрос.Заголовки.Вставить("X-API-Key", Константы.КешбекAPIКлюч.Получить());
Запрос.УстановитьТелоИзСтроки(ЗаписатьJSON(ТелоЗапроса));
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Synchronization during offline register operation
The register may work without a network. In this case, operations accumulate in the local 1C database and are sent in a batch when connection is restored. The Bitrix API accepts an array of transactions via POST /transactions/batch. Each transaction is processed independently; the response contains an array with results for each (success/error/duplicate).
Conflict: a user spent 500 rubles online while the register worked offline. The offline register attempted to debit another 300, but the balance was 500. During synchronization, Bitrix will detect that after the first debit the balance = 0, and will reject the offline operation with insufficient_balance. 1C must handle this case: cancel the discount or request additional payment.
User search
Customer identification offline is by phone number. Search in Bitrix:
private function getUserIdByPhone(string $phone): ?int
{
$phone = preg_replace('/\D/', '', $phone);
$result = \Bitrix\Main\UserTable::getList([
'filter' => ['PERSONAL_PHONE' => $phone],
'select' => ['ID'],
'limit' => 1,
]);
if ($row = $result->fetch()) {
return (int)$row['ID'];
}
// Search by additional field UF_PHONE_VERIFIED
$result = \Bitrix\Main\UserTable::getList([
'filter' => ['UF_PHONE_VERIFIED' => $phone],
'select' => ['ID'],
'limit' => 1,
]);
return ($row = $result->fetch()) ? (int)$row['ID'] : null;
}
Phone numbers are stored in different formats—normalization to 11 digits (without +, leading 7 or 8) is mandatory on input.
Displaying offline operations in the personal account
Transactions with SOURCE = '1c_retail' are displayed in history with a label "Purchase in store" instead of a link to the online order. In DESCRIPTION, 1C passes the store address or register number—this is shown to the user.
Synchronization monitoring
The table local_cashback_sync_log records all incoming requests from 1C: time, external_id, response status. If there were no operations from a specific store for N hours—a trigger notifies the administrator (possible failure in processing on the 1C side). The system maintains 99.9% sync accuracy and processes up to 500 transactions per second.
| Metric | Target |
|---|---|
| Time for offline operation to appear in Bitrix | < 5 minutes when register is online |
| Delay for batch sync after offline | < 10 minutes from connection recovery |
| Duplicate transactions | 0 (idempotency via external_id) |
API endpoints summary
| Method | Endpoint | Description |
|---|---|---|
| GET | /balance?user_phone=... | Get current balance for a user |
| POST | /transactions | Add a single transaction (accrual/debit) |
| POST | /transactions/batch | Add multiple transactions at once |
What is included in the work
- REST API on the Bitrix side: balance, transactions, batch
- Transaction tables with
EXTERNAL_IDandSOURCE - Idempotency logic, conflict handling for insufficient balance
- Phone normalization, user search
- External handler for 1C (coordinated with the 1C programmer)
- Synchronization monitoring, failure alerts
- Full deliverables include: REST API code, database migration scripts, 1C handler integration guide, administrator training session, and 6 months of warranty support
- Technical documentation and training of your staff
- Warranty support after launch
Timeline: 4–6 weeks if a 1C programmer is on the project. 6–10 weeks if developing the 1C handler from scratch. Cost is calculated individually after an audit of your system. With 30+ cashback integrations delivered and 5+ years on the market, we guarantee reliable sync. Get a consultation from an engineer. We will assess the project in one day.







