Разработка интеграции Битрикс24 с Kaiten
Kaiten — российский инструмент управления проектами с Kanban-досками, спринтами и временными треками. Компании используют его вместо Jira после ухода западных инструментов. Проблема та же, что и с любым PM-инструментом в связке с CRM: менеджеры в Битрикс24, команда в Kaiten, синхронизации нет — статусы расходятся, задачи дублируются.
Kaiten REST API
Kaiten предоставляет REST API, документация доступна на https://kaiten.io/api/. Аутентификация — Bearer-токен (Authorization: Bearer {token}). Токен создаётся в профиле пользователя или через OAuth для организации.
Ключевые эндпоинты:
| Метод | URL | Назначение |
|---|---|---|
| GET | /api/latest/cards |
Список карточек |
| POST | /api/latest/cards |
Создать карточку |
| PATCH | /api/latest/cards/{id} |
Обновить карточку |
| GET | /api/latest/boards |
Список досок |
| GET | /api/latest/columns |
Колонки доски |
| POST | /api/latest/cards/{id}/comments |
Добавить комментарий |
Kaiten поддерживает Webhooks — настраиваются в интерфейсе (Настройки → Вебхуки). Событие срабатывает при изменении карточки и отправляет POST на указанный URL с JSON-телом.
Класс-клиент для Kaiten API
namespace Local\KaitenIntegration;
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) ?? [];
}
}
Таблица маппинга и синхронизации
CREATE TABLE b_local_kaiten_task_map (
ID SERIAL PRIMARY KEY,
BITRIX_TASK_ID INTEGER NOT NULL,
KAITEN_CARD_ID INTEGER NOT NULL,
LAST_SYNC_AT TIMESTAMP,
SYNC_LOCK VARCHAR(36) DEFAULT NULL,
UNIQUE(BITRIX_TASK_ID),
UNIQUE(KAITEN_CARD_ID)
);
Синхронизация задач Битрикс24 → Kaiten
При изменении задачи в Битрикс24 (событие OnTaskUpdate) обновляем карточку в Kaiten:
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 из Kaiten → Битрикс24
Kaiten отправляет webhook при любом изменении карточки. Структура payload:
{
"event": "card.updated",
"data": {
"id": 12345,
"title": "Новое название задачи",
"column_id": 678,
"assignees": [{"id": 99, "username": "[email protected]"}]
}
}
Обработчик webhook:
// /local/kaiten/webhook/index.php
$body = file_get_contents('php://input');
$payload = json_decode($body, true);
// Валидация HMAC (если настроен secret в Kaiten)
$signature = $_SERVER['HTTP_X_KAITEN_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $body, KAITEN_WEBHOOK_SECRET);
if (!hash_equals($expected, $signature)) {
http_response_code(403);
exit;
}
$event = $payload['event'] ?? '';
$cardId = $payload['data']['id'] ?? null;
$columnId = $payload['data']['column_id'] ?? null;
if ($event !== 'card.updated' || !$cardId) {
http_response_code(200);
exit;
}
$map = KaitenTaskMapTable::getByKaitenId($cardId);
if (!$map || $map['SYNC_LOCK'] !== null) {
http_response_code(200);
exit;
}
KaitenTaskMapTable::update($map['ID'], ['SYNC_LOCK' => uniqid()]);
// Обновить статус задачи Битрикс24
if ($columnId) {
$b24Status = StatusMapper::kaitenColumnToB24Status($columnId);
$task = new \CTasks();
$task->Update($map['BITRIX_TASK_ID'], ['STATUS' => $b24Status], false);
}
KaitenTaskMapTable::update($map['ID'], ['SYNC_LOCK' => null]);
http_response_code(200);
Маппинг колонок Kaiten → статусы Битрикс24
В Kaiten вместо статусов — колонки с ID. Маппинг хранится в настройках модуля:
| Kaiten column_id | Название колонки | Битрикс24 STATUS |
|---|---|---|
| 101 | Бэклог | 1 (Новая) |
| 102 | В работе | 3 (В работе) |
| 103 | На ревью | 4 (Ждёт контроля) |
| 104 | Готово | 5 (Завершена) |
ID колонок получаем через GET /api/latest/boards/{boardId}/columns и храним в b_option.
Создание карточки из сделки CRM
Полезный сценарий: при переходе сделки на стадию «Разработка» — автоматически создать карточку в Kaiten на нужной доске. Реализуется через бизнес-процесс или через 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);
}
Сроки разработки
| Вариант | Состав | Срок |
|---|---|---|
| Односторонняя (Битрикс24 → Kaiten) | Создание и обновление карточек | 3–5 дней |
| Двусторонняя синхронизация | Статусы, комментарии, маппинг пользователей | 7–10 дней |
| С автосозданием из CRM | Воронка → создание карточки, отчёты | 10–14 дней |







