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

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

Jira — основная система управления задачами для dev- и CS-команд в большинстве B2B SaaS. Без интеграции с Kommo менеджер после Won вручную создаёт Jira-тикет, указывает тип клиента, план, технический контекст. При переходе тикета в Done Kommo не знает. Связка через Jira REST API замыкает этот цикл: Won -> Jira Task -> Done -> Note в сделку.

Почему нативная интеграция не закрывает задачу

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

Zapier поддерживает создание Jira-задачи при смене статуса в Kommo, но не умеет: заполнять описание тикета из нескольких кастомных полей, обрабатывать обратный вебхук от Jira, работать с custom field’ами Jira (Story Points, priority, epic).

Jira REST API v3 даёт полный контроль: создание задачи с любой структурой данных, управление переходами, чтение комментариев — всё через один API-ключ.

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

Kommo -> Jira:
— Название сделки -> Summary задачи
— Тариф и план -> labels или custom field
— Имя и email контакта -> Description задачи
— Технический стек из кастомного поля -> Components
— ID сделки -> custom field kommo_deal_id для трассировки

Jira -> Kommo:
— Задача перешла в Done -> Note в Kommo: «Онбординг завершён»
— Jira-комментарий от техкоманды -> Note в сделке
— Задача заблокирована (blocked) -> задача менеджеру: уточнить у клиента

Архитектура

Kommo Webhook: сделка перешла в Won
  ↓ Backend
  1. GET /api/v4/leads/{id} + contacts
     -> имя клиента, email, тариф, технический стек
  2. Jira: POST /rest/api/3/issue
     -> project: {key: 'OB'}, issuetype: {name: 'Task'}
     -> summary, description с данными клиента
     -> labels: ['onboarding', plan]
     -> customfield_10001: deal_id (Kommo Deal ID)
     -> получить issue_key (OB-123)
  3. Kommo: PATCH /leads/{deal_id}
     -> jira_ticket = 'OB-123'
     -> jira_ticket_url = 'https://company.atlassian.net/browse/OB-123'
  4. Kommo: POST /leads/{deal_id}/notes
     -> «Создан Jira-тикет OB-123: https://...»

Jira Webhook: issue_updated (transition to Done)
  ↓ Backend
  1. Из payload: issue.key, changelog.items (status change)
  2. Проверить: to.name == 'Done'
  3. Найти deal_id по customfield_10001 (kommo_deal_id)
  4. Kommo: POST /leads/{deal_id}/notes
     -> «Jira OB-123: задача закрыта. Онбординг завершён.»
  5. Опционально: PATCH /leads/{deal_id} -> jira_status = done

Jira REST API v3: ключевые запросы

Base URL: https://{site}.atlassian.net/rest/api/3/. Аутентификация: Basic Auth, username = email, password = api_token (создать в atlassian.com/manage-profile/security/api-tokens).

Создать задачу:

import requests
from requests.auth import HTTPBasicAuth

JIRA_URL = 'https://yourcompany.atlassian.net'
JIRA_EMAIL = 'bot@yourcompany.com'
JIRA_TOKEN = 'your_api_token'
JIRA_AUTH = HTTPBasicAuth(JIRA_EMAIL, JIRA_TOKEN)

def create_jira_issue(summary: str, description: str,
                      project_key: str, labels: list,
                      kommo_deal_id: int) -> str:
    payload = {
        'fields': {
            'project': {'key': project_key},
            'issuetype': {'name': 'Task'},
            'summary': summary,
            'description': {
                'type': 'doc',
                'version': 1,
                'content': [{
                    'type': 'paragraph',
                    'content': [{'type': 'text', 'text': description}]
                }]
            },
            'labels': labels,
            'priority': {'name': 'Medium'},
            # Кастомное поле - ID узнать через GET /rest/api/3/field
            'customfield_10200': str(kommo_deal_id)  # Kommo Deal ID
        }
    }

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

Описание задачи из данных Kommo:

def build_issue_description(lead: dict, contact: dict) -> str:
    plan = get_custom_field(lead, PLAN_FIELD_ID)
    tech_stack = get_custom_field(lead, TECH_STACK_FIELD_ID)
    deal_amount = lead.get('price', 0)

    return (
        f'Клиент: {contact.get("name")}\n'
        f'Email: {get_contact_email(contact)}\n'
        f'Тариф: {plan}\n'
        f'Сумма сделки: ${deal_amount}\n'
        f'Технический стек: {tech_stack}\n'
        f'Kommo Deal ID: {lead["id"]}'
    )

Обработка Jira Webhook:

from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/jira', methods=['POST'])
def jira_webhook():
    payload = request.json
    event = payload.get('webhookEvent')

    if event != 'jira:issue_updated':
        return '', 200

    issue = payload.get('issue', {})
    issue_key = issue.get('key')
    changelog = payload.get('changelog', {})

    # Ищем смену статуса в changelog
    status_change = None
    for item in changelog.get('items', []):
        if item.get('field') == 'status':
            status_change = item
            break

    if not status_change:
        return '', 200

    to_status = status_change.get('toString', '')
    if to_status != 'Done':
        return '', 200

    # Получить kommo_deal_id из кастомного поля Jira
    custom_fields = issue.get('fields', {})
    kommo_deal_id = custom_fields.get('customfield_10200')

    if kommo_deal_id:
        deal_id = int(kommo_deal_id)
        create_kommo_note(
            deal_id,
            f'Jira {issue_key}: задача закрыта (Done). Онбординг завершён.'
        )

    return '', 200

Получить список ID кастомных полей Jira:

def get_jira_fields():
    resp = requests.get(
        f'{JIRA_URL}/rest/api/3/field',
        auth=JIRA_AUTH
    )
    return {f['name']: f['id'] for f in resp.json()}

# Запустить один раз для поиска нужных field ID:
# fields = get_jira_fields()
# print(fields)  # {'Story Points': 'customfield_10016', ...}

Регистрация Jira Webhook:

Jira Cloud позволяет регистрировать webhooks через Admin: Settings -> System -> Webhooks -> Create a WebHook. Указать URL и выбрать события. Для Issue Updated выбрать: Issue updated с фильтром project = OB AND status = Done.

Также доступна регистрация через API: POST /rest/webhooks/1.0/webhook (требует Jira Admin права).

Маппинг тарифов Kommo -> Jira labels

PLAN_TO_LABELS = {
    'Starter':    ['onboarding', 'tier-starter'],
    'Pro':        ['onboarding', 'tier-pro'],
    'Enterprise': ['onboarding', 'tier-enterprise', 'priority-high'],
}

def get_jira_labels(kommo_plan: str) -> list:
    return PLAN_TO_LABELS.get(kommo_plan, ['onboarding'])

Enterprise-клиенты получают priority-high лейбл — CS-команда видит их в отдельном фильтре Jira.

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

B2B SaaS (EU, 30–40 новых клиентов в квартал, Kommo + Jira + Notion, команда онбординга 4 человека):

  • До: менеджер после Won тратил 15–20 минут на создание Jira-тикета. Нередко забывал указать тариф или технический стек — CS узнавал это уже в процессе онбординга. При закрытии тикета в Jira сделка в Kommo оставалась без обновления.
  • После: Won -> Jira Task за 40 секунд с полным описанием из CRM. CS-команда видит в тикете всё что нужно. При Done -> автоматический Note в Kommo. Sales Director видит статус онбординга прямо в сделке.
  • Дополнительно: если тикет висит без движения 7 дней — Jira-автоматизация (или scheduled webhook) -> задача менеджеру проверить.

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

  • B2B SaaS с отдельной командой онбординга / Customer Success / Implementation
  • После Won продажи передают клиента CS через Jira-задачи
  • CS-команда не работает в CRM, Sales-команда не работает в Jira
  • 15+ передач клиентов в месяц — ручное создание тикетов нерентабельно

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

Jira API — OAuth или Basic Auth?

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

Как Jira Webhook проверяет что запрос подлинный?

Jira Cloud не поддерживает HMAC-подпись webhooks по умолчанию. Защита — IP-whitelist (если у вас статический IP) или secret параметр в URL (Jira добавляет его к запросу). Дополнительно: проверяйте что payload содержит ожидаемый project key.

Как найти ID кастомного поля Jira для API?

Через GET /rest/api/3/field — возвращает все поля с их ID. Кастомные поля имеют ID вида customfield_XXXXX. Также ID виден в URL при редактировании поля в Jira: Settings -> Issues -> Custom Fields -> Edit field.

Что если один клиент имеет несколько сделок в Kommo?

В Jira-тикете храните kommo_deal_id конкретной сделки Won. При обратном вебхуке используете этот ID для обновления нужной сделки. Если сделок несколько — создавайте отдельный тикет для каждой Won.

Jira Server vs Jira Cloud — разные API?

Jira Cloud использует REST API v3 (atlassian.net). Jira Server/Data Center — REST API v2 (ваш домен). Основная разница: описание задачи в Cloud требует Atlassian Document Format (JSON-дерево), в Server — обычный текст или wiki-разметку.

Итого

  • Jira REST API v3: Basic Auth (email:api_token), Base URL https://{site}.atlassian.net/rest/api/3/
  • Создание задачи: POST /issue с полями из Kommo (summary, description, labels, custom fields)
  • Webhook при Done -> Note в Kommo через kommo_deal_id из Jira custom field
  • Описание задачи: Atlassian Document Format (ADF) для Jira Cloud
  • Типовой срок разработки — 1–2 недели

Если у вас Kommo и Jira и передача клиентов происходит вручную — опишите вашу структуру проектов и статусов. Exceltic.dev настроит создание тикетов и обратную синхронизацию статусов онбординга.

Ещё статьи

Все →