Bitrix24-Kaiten Integration Development
We often encounter this situation: managers work in Bitrix24, developers work in Kaiten, and tasks live their own lives. Statuses diverge, comments are duplicated, deadlines are missed. The result is lost leads and unnecessary meetings. One fintech company lost up to 15% of leads because managers forgot to update the status in Kaiten after closing a deal. After integration, task transfer time dropped from 2 hours to 2 minutes. Our experience shows that without synchronization between CRM and project management system, any Kanban tool loses its meaning.
Why Synchronization Between Bitrix24 and Kaiten Is a Problem
Kaiten is a Russian counterpart of Jira, popular after Western vendors left. It provides a REST API and webhooks, but setting up two-way synchronization requires deep understanding of both products. Typical mistakes:
-
Cyclic update: task changed in Bitrix24 → updated in Kaiten → webhook sent the change back, causing an infinite loop. Solution — lock via
SYNC_LOCKin the mapping table. - Data loss at scale: with 1000+ tasks per day, synchronization without queues leads to race conditions. We use locks and agents.
-
Different terminology: in Kaiten, statuses are board columns (
column_id), while in Bitrix24 they are numeric STATUS. Mapping is stored inb_optionand configured per specific board.
How We Implement the Integration
We develop the solution on PHP 8.1+ using Bitrix D7 ORM. The core is the KaitenApiClient class that encapsulates HTTP requests to Kaiten (key endpoints in the table below).
| Method | URL | Purpose |
|---|---|---|
| GET | /api/latest/cards |
List cards |
| POST | /api/latest/cards |
Create card |
| PATCH | /api/latest/cards/{id} |
Update card |
| GET | /api/latest/boards |
List boards |
| GET | /api/latest/columns |
Board columns |
| POST | /api/latest/cards/{id}/comments |
Add comment |
For storing relations, we use our own table b_local_kaiten_task_map with fields BITRIX_TASK_ID, KAITEN_CARD_ID and SYNC_LOCK for locking.
class KaitenApiClient { private string $baseUrl; private string $token; public function __construct(string $baseUrl, string $token) { $this->baseUrl = rtrim($baseUrl, '/'); $this->token = $token; } public function createCard(array $data): array { return $this->request('POST', '/api/latest/cards', $data); } public function updateCard(int $cardId, array $data): array { return $this->request('PATCH', "/api/latest/cards/{$cardId}", $data); } public function addComment(int $cardId, string $text): array { return $this->request('POST', "/api/latest/cards/{$cardId}/comments", ['text' => $text]); } private function request(string $method, string $path, array $data = []): array { $ch = curl_init($this->baseUrl . $path); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $this->token, 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => $method !== 'GET' ? json_encode($data) : null, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode >= 400) { throw new \RuntimeException("Kaiten API error {$httpCode}: {$response}"); } return json_decode($response, true) ?? []; } } What Is Included in the Work
- Analysis and design: describe synchronization scenarios (tasks, statuses, comments, files), agree on column mapping.
- Component development: Kaiten API client, Bitrix24 event handlers (
OnTaskUpdate,OnCrmDealUpdate), webhook handler. - Testing: verification on a test circuit with simulated load up to 500 tasks per hour.
- Deployment and training: configure on production, provide API endpoint documentation and administrator instructions.
- Warranty support: 1 month after delivery — bug fixes free of charge. Compliance certificate and solution license are provided.
Two-Way Synchronization: Cards and Webhook
When a task changes in Bitrix24 (event OnTaskUpdate), a handler updates the Kaiten card via PATCH /api/latest/cards/{id}. To avoid looping, we use SYNC_LOCK:
public static function onTaskUpdate(int $taskId, array $arFields): void { $map = KaitenTaskMapTable::getByBitrixId($taskId); if (!$map || $map['SYNC_LOCK'] !== null) { return; } KaitenTaskMapTable::update($map['ID'], ['SYNC_LOCK' => uniqid()]); try { $client = self::getClient(); $kaitenData = []; if (isset($arFields['TITLE'])) { $kaitenData['title'] = $arFields['TITLE']; } if (isset($arFields['DESCRIPTION'])) { $kaitenData['description'] = strip_tags($arFields['DESCRIPTION']); } if (isset($arFields['DEADLINE'])) { $kaitenData['due_date'] = date('Y-m-d', strtotime($arFields['DEADLINE'])); } if (isset($arFields['STATUS'])) { $columnId = StatusMapper::b24StatusToKaitenColumn($arFields['STATUS']); $kaitenData['column_id'] = $columnId; } if (!empty($kaitenData)) { $client->updateCard($map['KAITEN_CARD_ID'], $kaitenData); } KaitenTaskMapTable::update($map['ID'], [ 'SYNC_LOCK' => null, 'LAST_SYNC_AT' => new \Bitrix\Main\Type\DateTime(), ]); } catch (\Exception $e) { KaitenTaskMapTable::update($map['ID'], ['SYNC_LOCK' => null]); \Bitrix\Main\Diag\Debug::addToLog('KaitenSync: ' . $e->getMessage()); } } Webhook from Kaiten to Bitrix24 receives a JSON with the card.updated event:
{ "event": "card.updated", "data": { "id": 12345, "title": "New task title", "column_id": 678, "assignees": [{"id": 99, "username": "[email protected]"}] } } The handler verifies the HMAC signature and, if the lock is not active, updates the task status in Bitrix24 via CTasks::Update.
Column Mapping and Auto-Creation from CRM
Example mapping for the "Development" board:
| Kaiten column_id | Column name | Bitrix24 STATUS |
|---|---|---|
| 101 | Backlog | 1 (New) |
| 102 | In Progress | 3 (In Progress) |
| 103 | Review | 4 (Awaiting Control) |
| 104 | Done | 5 (Completed) |
A useful case is automatic Kaiten card creation when a CRM deal moves to the "Development" stage. Implemented via OnCrmDealUpdate:
if ($arFields['STAGE_ID'] === 'DEVELOPMENT') { $client = KaitenApiClientFactory::create(); $card = $client->createCard([ 'title' => 'Deal #' . $dealId . ': ' . $arFields['TITLE'], 'board_id' => KAITEN_DEV_BOARD_ID, 'column_id' => KAITEN_BACKLOG_COLUMN_ID, 'description' => $arFields['COMMENTS'], ]); \CCrmDeal::Update($dealId, ['UF_KAITEN_CARD_ID' => $card['id']], false); } Our approach saves up to 2 times the time compared to manual synchronization. Companies with integration experience confirm: automation eliminates human errors and speeds up task transfer between departments.
Development Timeline
| Option | Scope | Timeline |
|---|---|---|
| One-way (Bitrix24 → Kaiten) | Create and update cards | 3–5 days |
| Two-way synchronization | Statuses, comments, user mapping | 7–10 days |
| With auto-creation from CRM | Funnel → card creation, reports | 10–14 days |
Cost is calculated individually. Contact us — we will evaluate your project for free. Get a consultation on integration architecture and implementation timeline.
Over 5 years of experience in developing integrations for Bitrix24. Certified solutions, quality guarantee.
How to Verify Synchronization Correctness?
We recommend setting up monitoring: log each API call and check for errors. Use an agent that compares the number of tasks in both systems once an hour. If a discrepancy is found, an administrator is notified. This helps quickly identify failures and maintain data accuracy.
What Automation Brings Instead of Manual Transfer?
Manual task copying takes on average 10–15 minutes per task. With 20 tasks per day, that's 3–4 hours of work. Automation reduces this to seconds and eliminates typos and omissions. Moreover, statuses in Kaiten change automatically when a deal changes — the manager doesn't need to enter another interface.

