Kommo + Linear: автоматическое создание задач для разработки из выигранных сделок

Kommo + Linear: автоматическое создание задач для разработки из выигранных сделок

Linear — трекер задач для инженерных команд с GraphQL API вместо REST. Это принципиальное отличие от большинства других инструментов: единственный endpoint, все операции через мутации. Интеграция с Kommo решает конкретную проблему — при закрытии сделки нужный issue в Linear создаётся автоматически с данными из CRM, без ручного переноса контекста между командой продаж и разработкой.

Проблема: продажи и разработка работают в разных системах

Без интеграции:
— Won в Kommo -> менеджер пишет в Slack «создайте задачу для клиента X»
— Разработчик открывает Linear, вручную создаёт issue, копирует контекст из CRM
— Через неделю клиент уточняет детали — менеджер не знает статус задачи в Linear
— Сделка в Kommo не отражает прогресс внедрения: при следующем звонке нет актуальных данных

С интеграцией:
— Won в Kommo -> issue в Linear создаётся через 3 минуты с полными данными из сделки
— Изменение статуса задачи в Linear -> Note в карточке Kommo
— Менеджер видит прогресс в CRM без входа в трекер

Что синхронизируется

Kommo -> Linear:
— Название сделки -> title issue
— Описание / техзадание из кастомного поля -> description (поддерживает Markdown)
— Этап Won -> статус issue «In Progress» или «Backlog» в зависимости от процесса команды
— Email контакта и сумма -> добавляются в description для контекста
— Ответственный менеджер -> assigneeId через маппинг email -> Linear userId
— Срок из кастомного поля -> dueDate в ISO 8601
— ID сделки Kommo -> хранится в description или отдельном label для обратной трассировки

Linear -> Kommo:
— Issue завершён (статус «Done») -> Note в сделке Kommo
— Issue назначен новому исполнителю -> Note с именем для менеджера
— Идентификатор задачи (например, ENG-42) -> кастомное поле в карточке Kommo

Архитектура

Kommo Webhook: сделка перешла в Won
  ↓ Backend
  1. GET /api/v4/leads/{id} + contacts
     -> название, описание из кастомных полей, email, сумма
  2. Linear GraphQL: mutation issueCreate
     -> teamId + title + description + projectId + assigneeId + priority
     -> получить issue.id, issue.identifier (например ENG-42), issue.url
  3. Kommo: PATCH /leads/{id}
     -> кастомное поле linear_issue_id = ENG-42
  4. Kommo: POST /leads/{id}/notes
     -> «Создана задача в Linear: ENG-42. Ссылка: {url}»

Linear Webhook: issue.action = update, state.name = Done
  ↓ Backend
  1. Из payload: identifier, assignee, state
  2. Найти kommo_deal_id по linear_issue_id в хранилище (Redis / БД)
  3. Kommo: POST /leads/{deal_id}/notes
     -> «Задача ENG-42 закрыта в Linear. Исполнитель: {assignee}»

Linear GraphQL API: ключевые запросы

Endpoint: https://api.linear.app/graphql. Аутентификация: Authorization: Bearer <api_key>. Все запросы — POST с телом {"query": "..."} или {"query": "...", "variables": {...}}.

Получить список команд (нужен teamId):

import requests

LINEAR_URL = 'https://api.linear.app/graphql'
HEADERS = {
    'Authorization': f'Bearer {LINEAR_API_KEY}',
    'Content-Type': 'application/json'
}

def get_teams() -> list:
    query = """
    query {
        teams {
            nodes {
                id
                name
                key
            }
        }
    }
    """
    resp = requests.post(LINEAR_URL, json={'query': query}, headers=HEADERS)
    return resp.json()['data']['teams']['nodes']
    # [{'id': 'abc-123', 'name': 'Engineering', 'key': 'ENG'}, ...]

Создать issue из данных сделки:

def create_issue_from_deal(
    team_id: str,
    project_id: str,
    title: str,
    description: str,
    assignee_id: str = None,
    priority: int = 2  # 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low
) -> dict:
    mutation = """
    mutation IssueCreate($input: IssueCreateInput!) {
        issueCreate(input: $input) {
            success
            issue {
                id
                identifier
                url
            }
        }
    }
    """
    variables = {
        'input': {
            'title': title,
            'description': description,
            'teamId': team_id,
            'projectId': project_id,
            'priority': priority
        }
    }
    if assignee_id:
        variables['input']['assigneeId'] = assignee_id

    resp = requests.post(
        LINEAR_URL,
        json={'query': mutation, 'variables': variables},
        headers=HEADERS
    )
    result = resp.json()['data']['issueCreate']
    if result['success']:
        return result['issue']  # {'id': '...', 'identifier': 'ENG-42', 'url': '...'}
    raise RuntimeError(f'Linear issue creation failed: {resp.text}')

Получить проекты команды:

def get_team_projects(team_id: str) -> list:
    query = """
    query TeamProjects($teamId: String!) {
        team(id: $teamId) {
            projects {
                nodes {
                    id
                    name
                    state
                }
            }
        }
    }
    """
    resp = requests.post(
        LINEAR_URL,
        json={'query': query, 'variables': {'teamId': team_id}},
        headers=HEADERS
    )
    return resp.json()['data']['team']['projects']['nodes']

Обработка webhook Linear:

from flask import Flask, request
import hmac, hashlib

app = Flask(__name__)

@app.route('/webhooks/linear', methods=['POST'])
def linear_webhook():
    # Верификация подписи (рекомендуется для продакшн)
    signature = request.headers.get('X-Linear-Signature', '')
    secret = LINEAR_WEBHOOK_SECRET.encode()
    expected = hmac.new(secret, request.data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature):
        return 'Invalid signature', 401

    payload = request.json
    action = payload.get('action')      # 'create' | 'update' | 'remove'
    entity_type = payload.get('type')   # 'Issue' | 'Comment' | 'Project'
    data = payload.get('data', {})

    if entity_type == 'Issue' and action == 'update':
        state = data.get('state', {})
        if state.get('name') == 'Done':
            identifier = data.get('identifier')  # 'ENG-42'
            assignee = data.get('assignee', {}).get('name', 'неизвестен')

            deal_id = get_deal_id_by_linear_issue(identifier)
            if deal_id:
                create_kommo_note(
                    deal_id,
                    f'Задача {identifier} закрыта в Linear. Исполнитель: {assignee}.'
                )

    return '', 200

Настройка Linear webhook: Settings -> API -> Webhooks -> Create webhook. Указать URL, выбрать event types (Issue), команду. Webhook secret используется для верификации X-Linear-Signature HMAC-SHA256.

Маппинг полей Kommo -> Linear

KommoLinearПримечание
Название сделкиtitleПрямой маппинг
Кастомное поле «ТЗ»descriptionMarkdown поддерживается
Сумма сделкиВ descriptionLinear не имеет нативного поля «сумма»
Ответственный менеджерassigneeIdНужен маппинг email -> Linear userId
Срок из кастомного поляdueDateISO 8601, опциональное поле
Метка/тип сделкиpriority0=None, 1=Urgent, 2=High, 3=Medium, 4=Low

Маппинг пользователей — отдельный шаг конфигурации. В Linear нет поля «внешний ID», поэтому нужно хранить таблицу {kommo_user_email -> linear_user_id}. Получить пользователей:

query = """query { users { nodes { id name email } } }"""

Реальный кейс

B2B SaaS-компания (EU-рынок, 30–40 новых клиентов в квартал, отдельные команды продаж и разработки):

  • До: Won в Kommo -> менеджер отправлял письмо в разработку с описанием. Среднее время до создания задачи в Linear — 1–2 дня. Задачи нередко создавались без описания или с ошибками в приоритете.
  • После: Won -> issue в Linear через 3 минуты с полным описанием из поля «ТЗ», правильным проектом и приоритетом High. Менеджер видит Note «Задача ENG-42 создана» прямо в Kommo.
  • Дополнительный эффект: при завершении задачи менеджер автоматически получает сигнал для сервисного звонка клиенту — без отдельных напоминаний и Slack-сообщений.

Если команда использует ClickUp — там REST API, а не GraphQL: другой стек, аналогичная логика. Для нетехнических команд с более широкими workflow — смотрите Monday.com.

Для кого актуально

  • Команда разработки работает в Linear, продажи — в Kommo
  • Нужна автоматическая передача контекста сделки в задачу без ручного копирования
  • Won-клиент требует онбординга или технического внедрения — процесс должен стартовать немедленно
  • Важно видеть прогресс задачи в Linear прямо из карточки Kommo без переключения инструментов

Часто задаваемые вопросы

Linear API — REST или GraphQL?

GraphQL. Единственный endpoint https://api.linear.app/graphql, все операции — POST с телом запроса в формате GraphQL. Это отличает Linear от большинства SaaS-трекеров: Jira, Asana и ClickUp используют REST.

Как получить teamId и projectId для API?

Через GraphQL-запрос: query { teams { nodes { id name } } } — возвращает список команд с UUID. Аналогично для проектов: team(id: "...") { projects { nodes { id name } } }. Эти ID хранятся в конфигурации интеграции, не меняются.

Linear поддерживает приоритеты в issueCreate?

Да. Поле priority принимает: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low. При интеграции с Kommo удобно маппировать метку или тип сделки на приоритет задачи — например, «Корпоративный клиент» -> Urgent.

Как верифицировать webhook от Linear?

Linear подписывает payload через HMAC-SHA256. Заголовок X-Linear-Signature содержит hex-дайджест тела запроса, подписанного webhook secret. Проверка: hmac.new(secret, request.data, sha256).hexdigest() == signature. Это опционально, но обязательно для продакшн.

Нужен ли OAuth или достаточно API-ключа?

Для серверной интеграции достаточно personal API key: Settings -> API -> Personal API keys. OAuth 2.0 нужен только если вы пишете публичное приложение для множества Linear-аккаунтов. Для интеграции с одним воркспейсом — API key проще и надёжнее.

Итого

  • Linear GraphQL API: Bearer-токен, единственный endpoint, мутации issueCreate / issueUpdate
  • issueCreate принимает teamId, projectId, assigneeId, priority, dueDate — данные из сделки Kommo
  • Webhook payload: action + type + data — фильтруем по type=Issue, state.name=Done
  • Маппинг пользователей Kommo -> Linear по email — отдельный этап конфигурации
  • Типовой срок разработки — 1–2 недели

Если ваша команда разработки работает в Linear и продажи в Kommo — опишите схему: какие этапы воронки создают задачи, в какой проект и команду. Exceltic.dev настроит маппинг и обработку событий в обе стороны.

Ещё статьи

Все →