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

Todoist - популярный менеджер задач с REST API v2, используемый sales-специалистами для личного планирования. Kommo - CRM с воронкой продаж. Без интеграции задачи из Kommo не попадают в Todoist, а личные задачи менеджеров никак не связаны с карточками сделок. С интеграцией: при закрытии сделки автоматически создаётся набор задач онбординга в Todoist, при завершении задачи в Todoist - отметка в Kommo.

Todoist особенно популярен у менеджеров, работающих с мобильного устройства: iOS и Android приложения мгновенно синхронизируются, push-уведомления надёжные. Для B2B команд с циклом продаж 2-6 недель Todoist как персональный task manager дополняет Kommo: крупные задачи в CRM, ежедневный список дел - в Todoist.

Todoist REST API v2 - JSON API с Bearer token аутентификацией. Создание задач: POST /tasks, чтение: GET /tasks, завершение: POST /tasks/{task_id}/close. Webhook-события отправляются при создании, обновлении и завершении задач.

Архитектура интеграции

Kommo: сделка status_id = 142 (Выиграна)
  -> POST /api/v1/tasks (Todoist)
     Onboarding tasks for client X

Todoist: task completed
  -> POST /your-server/webhooks/todoist
     {event_name: item:completed, item: {id, content}}
  -> Kommo: POST /api/v4/leads/{id}/notes {text: "Todoist: завершено - task name"}

Реализация: создание задач онбординга при победе в сделке

import requests, os, hmac, hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)

KOMMO_DOMAIN    = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN     = os.environ["KOMMO_TOKEN"]
TODOIST_TOKEN   = os.environ["TODOIST_API_TOKEN"]
TODOIST_SECRET  = os.environ["TODOIST_CLIENT_SECRET"]  # для webhook верификации

KOMMO_BASE = f"https://{KOMMO_DOMAIN}/api/v4"
KOMMO_HDR  = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
TD_BASE    = "https://api.todoist.com/rest/v2"
TD_HDR     = {"Authorization": f"Bearer {TODOIST_TOKEN}"}

KOMMO_WON_STATUS = 142

# Шаблон задач онбординга после закрытия сделки
ONBOARDING_TASKS = [
    ("Отправить приветственное письмо клиенту",     1, 0),   # (title, priority, offset_days)
    ("Запланировать kickoff-встречу",                2, 0),
    ("Настроить доступы и onboarding-ресурсы",       2, 1),
    ("Первый check-in с клиентом (конец 1-й недели)", 1, 7),
    ("Review по итогам первого месяца",              1, 30),
]

@app.route("/webhooks/kommo", methods=["POST"])
def kommo_event():
    import time
    data = request.json or {}
    for lead in data.get("leads", {}).get("status", []):
        if lead.get("status_id") == KOMMO_WON_STATUS:
            create_onboarding_tasks(lead["id"])
    return "ok", 200

def get_lead_info(lead_id: int) -> dict:
    r = requests.get(f"{KOMMO_BASE}/leads/{lead_id}", headers=KOMMO_HDR, params={"with": "contacts"})
    return r.json() if r.ok else {}

def create_todoist_project(name: str) -> str | None:
    r = requests.post(
        f"{TD_BASE}/projects",
        headers=TD_HDR,
        json={"name": name},
    )
    return r.json().get("id") if r.ok else None

def create_onboarding_tasks(lead_id: int):
    from datetime import date, timedelta

    lead   = get_lead_info(lead_id)
    client = lead.get("name", f"Lead {lead_id}")

    # Создать проект под клиента
    project_id = create_todoist_project(f"Onboarding: {client}")

    today = date.today()

    for title, priority, offset_days in ONBOARDING_TASKS:
        due_date = today + timedelta(days=offset_days)
        payload = {
            "content":    f"{title} - {client}",
            "priority":   priority,  # 1=normal, 2=high, 3=urgent, 4=very urgent
            "due_string": due_date.strftime("%Y-%m-%d"),
            "description": f"Kommo Lead ID: {lead_id}",
        }
        if project_id:
            payload["project_id"] = project_id

        requests.post(f"{TD_BASE}/tasks", headers=TD_HDR, json=payload)

    # Заметка в Kommo
    requests.post(
        f"{KOMMO_BASE}/leads/{lead_id}/notes",
        headers=KOMMO_HDR,
        json=[{"note_type": "common", "params": {"text": f"Todoist: создано {len(ONBOARDING_TASKS)} задач онбординга для {client}"}}],
    )

Реализация: Todoist webhook -> Kommo

Todoist отправляет webhook-события через App Console (Apps -> Your App -> Webhooks). Верификация через HMAC-SHA256:

@app.route("/webhooks/todoist", methods=["POST"])
def todoist_event():
    # Верификация подписи
    user_agent = request.headers.get("X-Todoist-Hmac-SHA256", "")
    raw_body   = request.data
    computed   = hmac.new(
        TODOIST_SECRET.encode(), raw_body, hashlib.sha256
    ).digest()
    import base64
    computed_b64 = base64.b64encode(computed).decode()

    if not hmac.compare_digest(computed_b64, user_agent):
        return "unauthorized", 401

    data       = request.json or {}
    event_name = data.get("event_name", "")
    item       = data.get("event_data", {})

    if event_name == "item:completed":
        task_title   = item.get("content", "")
        task_id      = item.get("id", "")
        description  = item.get("description", "")

        # В description хранили Kommo Lead ID: "Kommo Lead ID: 123456"
        lead_id = extract_lead_id_from_description(description)
        if lead_id:
            requests.post(
                f"{KOMMO_BASE}/leads/{lead_id}/notes",
                headers=KOMMO_HDR,
                json=[{"note_type": "common", "params": {
                    "text": f"Todoist: завершено - {task_title}"
                }}],
            )
    return "ok", 200

def extract_lead_id_from_description(description: str) -> int | None:
    prefix = "Kommo Lead ID: "
    if prefix in description:
        try:
            return int(description.split(prefix)[1].strip().split()[0])
        except (ValueError, IndexError):
            return None
    return None

Дополнительные сценарии

Задача в Todoist при любой смене статуса (не только Won):

Настройте маппинг стадий на задачи:

  • “КП отправлено” -> задача “Follow up через 3 дня” (due: +3 дня)
  • “Счёт выставлен” -> задача “Контроль оплаты” (due: +5 дней)
  • “Потеря сделки” -> задача “Анализ причины проигрыша”

Синхронизация статусов задач Kommo -> Todoist:

При создании задачи в Kommo через API - также создать зеркальную задачу в Todoist. Для этого нужно подписаться на task webhook-события Kommo.

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

IT-интегратор, команда 5 менеджеров. До интеграции: менеджеры вручную создавали задачи в Todoist после каждой выигранной сделки - 8-10 минут на сделку при 10-15 победах в месяц. После интеграции: автоматически создаётся 5 задач онбординга с датами, привязанными к дате закрытия. Менеджер видит задачу в Todoist мгновенно без переключения интерфейсов.

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

Менеджеры по продажам, активно использующие Todoist как персональный инструмент планирования. Компании с чётким онбординг-процессом после закрытия сделки.

Альтернативный подход - создание задач в ClickUp описан в Kommo + ClickUp: задачи и лиды без Zapier.

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

Можно ли создавать задачи в разделах (Sections) проекта?

Да. Сначала создайте секции через POST /sections с project_id, затем при создании задачи указывайте section_id. Для онбординга можно создавать секции: “Неделя 1”, “Неделя 2”, “Месяц 1”.

Как избежать дублирования задач если webhook сработал дважды?

Добавьте идемпотентный ключ: перед созданием задач проверьте через GET /tasks?filter=#{lead_id} (если вы используете label с lead_id). Или храните список уже обработанных lead_id в кэше (Redis) с TTL 24h.

Todoist бесплатный план поддерживает webhook?

Webhook-события в Todoist доступны только через OAuth App в Todoist App Console - это требует создания приложения в Todoist, что бесплатно. Personal Access Token для API работает на всех планах.

Итог

Kommo + Todoist:

  • Kommo Won event -> создать проект + набор задач с датами в Todoist
  • В description задачи - Kommo Lead ID: {id} для обратной связи
  • Todoist item:completed webhook -> заметка в Kommo
  • HMAC-SHA256 верификация через X-Todoist-Hmac-SHA256 (base64-encoded)

Если нужна интеграция Kommo с Todoist под ваш onboarding-процесс - опишите задачу команде Exceltic.dev.

Ещё статьи

Все →