Обсудить задачу

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

Когда сделка переходит в «Выиграна», Kommo отправляет webhook на ваш backend, backend вызывает POST /rest/api/3/issue Jira REST API v3 и возвращает ключ задачи (например, DEV-47) обратно в карточку Kommo. Всё это занимает около 3 секунд и не требует участия менеджера.

Это не про Zapier и не про Kommo Marketplace. Это про прямое подключение двух API с контролем над каждым полем - от ADF-описания задачи до кастомных полей Jira и идемпотентности при ретраях.

Компании, которые работают на Kommo и ведут разработку в Jira, часто сталкиваются с одним и тем же разрывом: продажи закрывают сделку и ждут. Разработчики не знают, что появился новый клиент, пока кто-то не напишет им вручную. В интеграционных проектах команды Exceltic.dev эта задержка стабильно составляет 1-2 рабочих дня, а при высокой загрузке продаж - и больше. В этой статье разбираем полную схему: webhook -> создание issue -> обратная запись key в CRM, включая обработку дублей.

Почему нативного решения нет

Kommo Marketplace содержит Jira-виджеты, но они решают другую задачу - отображение Jira-тикета в карточке. Создать issue с данными из нескольких полей сделки (сумма, контакт, кастомные поля) через эти виджеты нельзя. Двусторонней синхронизации нет - статус задачи в Jira не попадает обратно в Kommo.

Zapier поддерживает создание Jira-задачи при смене статуса в Kommo, но при объёме 20+ сделок в месяц стоимость и надёжность Zapier становятся проблемой: polling-модель Zapier задерживает обработку события на 5-15 минут и не гарантирует идемпотентность при ретраях. Для команд, у которых POST /rest/api/3/issue должен срабатывать ровно один раз - это неприемлемо.

Zapier - инструмент быстрого прототипирования, не производственной интеграции с гарантиями.

Что реализуется в кастомной интеграции

Webhook Kommo на переход в статус «Выиграна»

Kommo отправляет webhook при смене статуса сделки. Событие - leads.status, в payload передаётся id сделки, status_id (новый), old_status_id, pipeline_id, price. Тип события leads.status нужно зарегистрировать в Kommo: Settings -> Integrations -> Webhooks.

Идентификация «выигранной» сделки - по связке pipeline_id + status_id. В Kommo нет единого «won» статуса - у каждого пайплайна свои ID стадий. Получить нужный ID: Settings -> Pipelines -> нажать на стадию «Выиграна» -> в URL появится status_id. Либо через API: GET /api/v4/leads/pipelines - там все стадии с полями id и type (тип 142 = «Выиграна» в стандартных пайплайнах Kommo).

После получения webhook backend запрашивает полные данные сделки через GET /api/v4/leads/{id}?with=contacts,companies - так получаем контакт и кастомные поля, которые не входят в webhook payload.

Jira REST API v3: создание issue с данными из сделки

Аутентификация: Basic Auth - email пользователя Jira + API Token. API Token создаётся по адресу id.atlassian.com/manage-profile/security/api-tokens. Важно: токен привязан к пользователю, а не к проекту. Для интеграций используйте сервисный аккаунт, не личный.

Строка для Basic Auth: base64(email:api_token), передаётся в заголовке Authorization: Basic <encoded>.

Base URL: https://{your-site}.atlassian.net/rest/api/3/.

Минимальный payload для создания issue:

import requests
from requests.auth import HTTPBasicAuth
import base64

JIRA_URL = 'https://yourcompany.atlassian.net'
JIRA_AUTH = HTTPBasicAuth('[email protected]', 'your_api_token')

def create_jira_issue_from_deal(lead: dict, contact: dict) -> str:
    """
    Создаёт Jira issue из данных сделки Kommo.
    Возвращает ключ задачи, например 'DEV-47'.
    """
    deal_amount = lead.get('price', 0)
    contact_name = contact.get('name', 'Неизвестный контакт')
    contact_email = get_contact_email(contact)
    deal_name = lead.get('name', f'Сделка #{lead["id"]}')

    # Описание в Atlassian Document Format (ADF) - обязательно для Jira Cloud
    description_text = (
        f'Клиент: {contact_name}\n'
        f'Email: {contact_email}\n'
        f'Сумма сделки: ${deal_amount}\n'
        f'Kommo Deal ID: {lead["id"]}\n'
        f'Менеджер: {lead.get("responsible_user_id")}'
    )

    payload = {
        'fields': {
            'project': {'key': 'DEV'},         # ключ проекта Jira
            'issuetype': {'name': 'Task'},      # Task, Story, Bug, etc.
            'summary': f'[CRM] {deal_name}',
            'description': {
                'type': 'doc',
                'version': 1,
                'content': [{
                    'type': 'paragraph',
                    'content': [{
                        'type': 'text',
                        'text': description_text
                    }]
                }]
            },
            'priority': {'name': 'Medium'},
            # кастомное поле для хранения Kommo Deal ID
            # ID кастомного поля узнать через GET /rest/api/3/field
            'customfield_10200': str(lead['id'])
        }
    }

    resp = requests.post(
        f'{JIRA_URL}/rest/api/3/issue',
        auth=JIRA_AUTH,
        json=payload
    )
    resp.raise_for_status()
    return resp.json()['key']  # 'DEV-47'

Для получения ID кастомного поля под kommo_deal_id - один раз вызвать GET /rest/api/3/field и найти нужное поле в ответе. ID вида customfield_XXXXX. Это нужно для обратной трассировки: когда задача закрывается в Jira, нам нужно найти сделку в Kommo.

Обратная запись Jira issue key в Kommo

После создания задачи backend записывает ключ обратно в Kommo через PATCH /api/v4/leads/{id} - в кастомное поле jira_ticket_key. Затем добавляет Note: POST /api/v4/leads/{id}/notes. Менеджер видит ключ прямо в карточке без входа в Jira.

def write_jira_key_to_kommo(
    deal_id: int,
    jira_key: str,
    jira_base_url: str,
    kommo_access_token: str
) -> None:
    headers = {'Authorization': f'Bearer {kommo_access_token}'}
    jira_url = f'{jira_base_url}/browse/{jira_key}'

    # Записать ключ в кастомное поле Kommo
    requests.patch(
        f'https://yourcommo.kommo.com/api/v4/leads/{deal_id}',
        headers=headers,
        json={'custom_fields_values': [{
            'field_id': JIRA_KEY_FIELD_ID,
            'values': [{'value': jira_key}]
        }]}
    )

    # Добавить заметку в карточку
    requests.post(
        f'https://yourcommo.kommo.com/api/v4/leads/{deal_id}/notes',
        headers=headers,
        json=[{'note_type': 4, 'params': {
            'text': f'Создана задача в Jira: {jira_key} - {jira_url}'
        }}]
    )

Идемпотентность: защита от дублей

Webhook Kommo может прийти дважды - при нестабильном соединении или при повторной смене статуса. Без защиты каждый webhook создаст новую задачу в Jira.

Решение - хранить маппинг kommo_deal_id -> jira_issue_key в Redis или PostgreSQL. Перед вызовом POST /rest/api/3/issue проверять: если для данного deal_id уже есть ключ - пропустить создание, вернуть существующий ключ. Это стандартный паттерн идемпотентности для webhook-обработчиков.

def handle_kommo_webhook(payload: dict) -> None:
    lead_data = payload.get('leads', {}).get('status', [])
    if not lead_data:
        return

    lead = lead_data[0]
    deal_id = int(lead['id'])
    pipeline_id = int(lead.get('pipeline_id', 0))
    status_id = int(lead.get('status_id', 0))

    # Проверить что это именно Won-стадия нужного пайплайна
    if not is_won_status(pipeline_id, status_id):
        return

    # Идемпотентность: проверить существующий ключ
    existing_key = get_jira_key_for_deal(deal_id)  # из Redis/БД
    if existing_key:
        return  # дубль, пропускаем

    # Получить полные данные сделки
    full_lead, contact = fetch_lead_with_contact(deal_id)

    # Создать issue в Jira
    jira_key = create_jira_issue_from_deal(full_lead, contact)

    # Сохранить маппинг
    save_jira_key_for_deal(deal_id, jira_key)

    # Записать результат в Kommo
    write_jira_key_to_kommo(deal_id, jira_key, JIRA_URL, KOMMO_TOKEN)

Пошаговая схема

  1. Сделка переходит в стадию «Выиграна» в Kommo
  2. Kommo отправляет POST на ваш endpoint с событием leads.status
  3. Backend проверяет pipeline_id + status_id - это Won?
  4. Проверка идемпотентности: есть уже созданная задача для этого deal_id?
  5. Если нет - GET /api/v4/leads/{id}?with=contacts для получения полных данных
  6. POST /rest/api/3/issue в Jira с ADF-описанием и кастомными полями
  7. Jira возвращает key (например, DEV-47)
  8. Сохранить маппинг deal_id -> DEV-47 в хранилище
  9. PATCH /api/v4/leads/{id} - записать ключ в кастомное поле Kommo
  10. POST /api/v4/leads/{id}/notes - Note менеджеру: «Создана задача DEV-47»
  11. Webhook от Jira при закрытии задачи (Done) -> Note в Kommo: «Задача DEV-47 закрыта»

Реальный кейс с цифрами

B2B SaaS компания (Европа, 25-35 новых клиентов в месяц): команда продаж в Kommo, команда разработки и CS в Jira. Задержка передачи клиента после Won составляла в среднем 1.5 дня - менеджер закрывал сделку, писал письмо или Slack-сообщение, разработчик создавал тикет вручную с той информацией, которую успевал собрать.

После внедрения кастомной интеграции:

  • Время от Won до появления задачи в Jira: под 10 секунд (включая webhook delivery)
  • 0 задач с пустым описанием - контакт, email, сумма, plan заполняются автоматически из полей сделки
  • Менеджер видит статус задачи (In Progress / Done) прямо в карточке Kommo через обратный webhook
  • Экономия: около 20 минут в расчёте на одну сделку (ручное создание + поиск информации + уточнения)

При 30 сделках в месяц это около 10 часов в месяц - только на передаче контекста между командами. Плюс устранение ошибок из-за неполной информации в задачах.

Для сравнения - у команды Exceltic.dev также была интеграция Kommo с ClickUp для другого клиента, где логика аналогична, но REST API ClickUp проще - нет ADF-формата описания.

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

Эта интеграция нужна, если:

  • Продажи ведёте в Kommo, задачи разработки / CS / внедрения - в Jira
  • После Won клиент требует технического онбординга, разработки или настройки - и это фиксируется задачей
  • 15+ передач клиентов в месяц - ручное создание тикетов занимает ощутимое время
  • Нужна двусторонняя связь: менеджер должен видеть статус Jira-задачи прямо в CRM

Zapier-вариант не подходит, если у вас строгие требования к идемпотентности или объём достаточен чтобы оправдать кастомную разработку.

Подробнее о том, как вообще строить интеграции и разработку под Kommo - в отдельном разделе.

Термин: Atlassian Document Format (ADF) - JSON-структура для описания rich text в Jira Cloud. В отличие от Jira Server/Data Center, где description принимает plaintext, Jira Cloud требует ADF-документ с явным type: doc, version: 1 и деревом content. Без правильного формата POST /rest/api/3/issue вернёт 400 Bad Request.

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

Чем кастомная интеграция лучше Zapier для Kommo -> Jira?

Zapier работает по polling-модели с интервалом 5-15 минут, не гарантирует идемпотентность при сбоях и не умеет заполнять Jira custom fields из нескольких полей сделки одновременно. Кастомный webhook-обработчик реагирует на событие в реальном времени, поддерживает идемпотентность через хранилище и дает полный контроль над структурой issue - включая ADF-описание, labels, priority и кастомные поля.

Как Kommo передаёт данные о выигранной сделке?

Kommo отправляет POST-запрос с событием leads.status в payload. Поля в webhook payload: id (deal ID), status_id (новый статус), old_status_id, pipeline_id, price. Контакты и кастомные поля в webhook не входят - их нужно запрашивать отдельно через GET /api/v4/leads/{id}?with=contacts. Стадия «Выиграна» идентифицируется по связке pipeline_id + status_id, или через type: 142 в ответе GET /api/v4/leads/pipelines.

Нужен ли OAuth для Jira API или достаточно Basic Auth?

Для серверной интеграции без участия пользователя - достаточно Basic Auth с email и API Token. API Token создаётся по адресу id.atlassian.com/manage-profile/security/api-tokens. OAuth 2.0 нужен только для публичных приложений, где каждый пользователь авторизует доступ от своего имени. По состоянию на 2026 год Atlassian планировал ввести истечение срока действия API токенов - проверяйте актуальную политику в документации Atlassian.

Что такое ADF и зачем он нужен в Jira Cloud?

ADF (Atlassian Document Format) - обязательный формат для поля description в Jira Cloud REST API v3. Минимальная структура: {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "текст"}]}]}. Если передать plain string - API вернёт 400. В Jira Server/Data Center (не cloud) description принимает plain text или wiki-markup - это другое API.

Как избежать дублей при повторных webhook от Kommo?

Hранить маппинг kommo_deal_id -> jira_issue_key в Redis или PostgreSQL. В начале обработки каждого webhook проверять: если запись уже есть - пропустить создание задачи. Это стандартный паттерн идемпотентности. Дополнительно: Kommo может прислать webhook повторно при сетевой ошибке (ответ не 200 в течение 2 секунд) - поэтому endpoint должен возвращать 200 даже при пропуске дубля.

Что если нам нужна интеграция с Kommo и другими трекерами задач?

Логика идентична для большинства task-трекеров: webhook Kommo на Won -> вызов API трекера -> обратная запись ID задачи в CRM. Разница только в деталях API: ClickUp использует REST с простым JSON-телом, Linear - GraphQL с мутациями. Jira отличается обязательным ADF-форматом описания и необходимостью хранить custom field ID для трассировки.

Итого

  • Kommo webhook leads.status + проверка pipeline_id + status_id - надёжная идентификация Won
  • Jira REST API v3: Basic Auth (email + API Token), POST /rest/api/3/issue с обязательным ADF-описанием
  • Идемпотентность через хранилище deal_id -> issue_key - защита от дублей при ретраях
  • Обратная запись issue_key в Kommo через PATCH /leads/{id} и Note - менеджер видит статус задачи без Jira
  • Типовой срок разработки: 1-2 недели с учётом конфигурации маппинга полей

Если у вас Kommo и Jira и передача клиентов происходит вручную - опишите вашу структуру: какие проекты Jira, какие поля сделки нужны в задаче. Команда Exceltic.dev разберёт архитектуру и оценит объём работ.

Ещё статьи

Все →