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
| Kommo | Linear | Примечание |
|---|---|---|
| Название сделки | title | Прямой маппинг |
| Кастомное поле «ТЗ» | description | Markdown поддерживается |
| Сумма сделки | В description | Linear не имеет нативного поля «сумма» |
| Ответственный менеджер | assigneeId | Нужен маппинг email -> Linear userId |
| Срок из кастомного поля | dueDate | ISO 8601, опциональное поле |
| Метка/тип сделки | priority | 0=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 настроит маппинг и обработку событий в обе стороны.