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

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

Rocketlane - платформа для клиентского онбординга и project delivery. Её главное отличие от Jira или Asana - customer portal: клиент видит прогресс своего онбординга в реальном времени, может общаться с командой в контексте задач, загружать документы. Это снижает нагрузку на CSM (Customer Success Manager) и создаёт прозрачный процесс для клиента.

Нативной интеграции Rocketlane + Kommo не существует. Типичная проблема: сделка закрывается в Kommo, продажник сообщает в Slack «клиент Acme Corp подписал контракт», CSM вручную создаёт проект в Rocketlane, вводит данные клиента, добавляет участников. Проходит 2-3 дня до начала фактического онбординга. Клиент в этот период не получает никакой информации.

Связка кастомных интеграций Kommo с Rocketlane API решает эту проблему: проект создаётся автоматически в момент закрытия сделки.

Что реализуется - архитектура решения

Kommo: сделка переходит в статус Won
    --> Webhook --> Python сервис
        --> Rocketlane API: POST /projects (из template_id)
        --> Rocketlane API: POST /projects/{id}/members (добавить клиента)
        --> Kommo API: добавить Note о созданном проекте

Rocketlane: milestone.completed
    --> Webhook --> Python сервис
        --> Kommo API: добавить Note в сделку

Технические детали

Rocketlane API Auth. Bearer token. Получается в Rocketlane Settings -> API -> Generate Token. Передаётся в заголовке Authorization: Bearer {token}.

Rocketlane API эндпоинты:

  • POST /v1/projects - создать проект. Параметры: name, templateId, startDate, description
  • GET /v1/templates - список шаблонов проектов
  • POST /v1/projects/{id}/members - добавить участника. Параметры: email, role (customer/team)
  • GET /v1/projects/{id}/milestones - список milestone
  • Webhooks: настраиваются в Rocketlane Settings -> Integrations -> Webhooks

Won-статус в Kommo. В Kommo каждая воронка имеет специальный статус «Победа» (Won). Его ID можно найти через GET /api/v4/pipelines/{pipeline_id}/statuses. Статус Won имеет тип is_final: true и type: won.

Customer Portal Rocketlane. При добавлении участника с role: customer Rocketlane автоматически отправляет email-приглашение на портал. Клиент получает персональную ссылку и видит план онбординга.

Пошаговая реализация

Шаг 1. Определение Won-статуса

import os
import requests

KOMMO_DOMAIN = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
ROCKETLANE_TOKEN = os.environ["ROCKETLANE_TOKEN"]
ROCKETLANE_BASE = "https://api.rocketlane.com/api/v1"


def get_won_status_ids() -> set[int]:
    """Получаем все ID Won-статусов из всех воронок Kommo."""
    url = f"https://{KOMMO_DOMAIN}/api/v4/pipelines"
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    r = requests.get(url, params={"with": "statuses"}, headers=headers, timeout=10)
    if not r.ok:
        return set()

    won_ids = set()
    pipelines = r.json().get("_embedded", {}).get("pipelines", [])
    for pipeline in pipelines:
        statuses = pipeline.get("_embedded", {}).get("statuses", [])
        for status in statuses:
            if status.get("type") == 142:  # 142 = Won в Kommo
                won_ids.add(status["id"])
    return won_ids

Шаг 2. Создание проекта Rocketlane из шаблона

from flask import Flask, request
from datetime import datetime, timedelta

app = Flask(__name__)

ROCKETLANE_TEMPLATE_ID = os.environ.get("ROCKETLANE_TEMPLATE_ID", "")
# Кэшируем Won-статусы при старте
WON_STATUS_IDS = get_won_status_ids()


def get_lead_data(lead_id: int) -> dict:
    """Получаем данные сделки включая контакты и компанию."""
    url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}"
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    r = requests.get(url, params={"with": "contacts,companies"}, headers=headers, timeout=10)
    r.raise_for_status()
    return r.json()


def get_contact_email_and_name(contact_id: int) -> tuple[str, str]:
    """Получаем email и имя контакта."""
    url = f"https://{KOMMO_DOMAIN}/api/v4/contacts/{contact_id}"
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    r = requests.get(url, headers=headers, timeout=10)
    if not r.ok:
        return "", ""

    contact = r.json()
    name = contact.get("name", "")
    email = ""
    for cf in contact.get("custom_fields_values", []) or []:
        if cf.get("field_code") == "EMAIL":
            email = cf["values"][0]["value"]
            break
    return email, name


def create_rocketlane_project(project_name: str, template_id: str) -> dict | None:
    """Создаём проект в Rocketlane из шаблона."""
    url = f"{ROCKETLANE_BASE}/projects"
    headers = {
        "Authorization": f"Bearer {ROCKETLANE_TOKEN}",
        "Content-Type": "application/json",
    }
    start_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
    payload = {
        "name": project_name,
        "templateId": template_id,
        "startDate": start_date,
        "status": "active",
    }
    r = requests.post(url, json=payload, headers=headers, timeout=30)
    if not r.ok:
        print(f"Rocketlane project creation failed: {r.status_code} {r.text}")
        return None
    return r.json()


def add_customer_to_project(project_id: str, email: str, name: str) -> bool:
    """Добавляем клиента в проект Rocketlane (получит приглашение на portal)."""
    url = f"{ROCKETLANE_BASE}/projects/{project_id}/members"
    headers = {
        "Authorization": f"Bearer {ROCKETLANE_TOKEN}",
        "Content-Type": "application/json",
    }
    first, *last_parts = name.split(" ", 1)
    payload = {
        "email": email,
        "role": "customer",
        "firstName": first,
        "lastName": last_parts[0] if last_parts else "",
    }
    r = requests.post(url, json=payload, headers=headers, timeout=10)
    return r.ok


def add_kommo_note(lead_id: int, text: str):
    url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}/notes"
    headers = {
        "Authorization": f"Bearer {KOMMO_TOKEN}",
        "Content-Type": "application/json",
    }
    payload = [{"note_type": "common", "params": {"text": text}}]
    requests.post(url, json=payload, headers=headers, timeout=10)


@app.route("/webhooks/kommo/won", methods=["POST"])
def kommo_won_webhook():
    """Обрабатываем переход сделки в Won."""
    data = request.form
    lead_id = int(data.get("leads[status][0][id]", 0))
    new_status_id = int(data.get("leads[status][0][status_id]", 0))

    if not lead_id or new_status_id not in WON_STATUS_IDS:
        return {"ok": True}

    lead = get_lead_data(lead_id)
    lead_name = lead.get("name", f"Сделка #{lead_id}")

    # Получаем данные основного контакта
    contacts = lead.get("_embedded", {}).get("contacts", [])
    if not contacts:
        add_kommo_note(lead_id, "Rocketlane: не удалось создать проект - нет контакта в сделке")
        return {"ok": True}

    contact_id = contacts[0]["id"]
    email, name = get_contact_email_and_name(contact_id)

    if not email:
        add_kommo_note(lead_id, "Rocketlane: не удалось создать проект - нет email у контакта")
        return {"ok": True}

    # Получаем название компании
    companies = lead.get("_embedded", {}).get("companies", [])
    company_name = companies[0].get("name", "") if companies else ""
    project_name = f"Онбординг: {company_name or name}"

    # Создаём проект
    project = create_rocketlane_project(project_name, ROCKETLANE_TEMPLATE_ID)
    if not project:
        add_kommo_note(lead_id, "Rocketlane: ошибка при создании проекта")
        return {"ok": True}

    project_id = project["id"]
    project_url = project.get("portalUrl", "")

    # Добавляем клиента
    add_customer_to_project(project_id, email, name)

    # Фиксируем в Kommo
    note_text = (
        f"Rocketlane: проект онбординга создан\n"
        f"Проект: {project_name}\n"
        f"ID: {project_id}\n"
        f"Portal: {project_url}\n"
        f"Клиент добавлен: {email}"
    )
    add_kommo_note(lead_id, note_text)

    # Сохраняем ID проекта в кастомное поле Kommo
    ROCKETLANE_FIELD_ID = int(os.environ.get("KOMMO_ROCKETLANE_FIELD_ID", 0))
    if ROCKETLANE_FIELD_ID:
        patch_url = f"https://{KOMMO_DOMAIN}/api/v4/leads"
        headers = {
            "Authorization": f"Bearer {KOMMO_TOKEN}",
            "Content-Type": "application/json",
        }
        requests.patch(patch_url, json=[{
            "id": lead_id,
            "custom_fields_values": [{
                "field_id": ROCKETLANE_FIELD_ID,
                "values": [{"value": project_id}]
            }]
        }], headers=headers, timeout=10)

    return {"ok": True}


@app.route("/webhooks/rocketlane", methods=["POST"])
def rocketlane_webhook():
    """Обрабатываем события Rocketlane (milestone completed)."""
    event = request.json
    event_type = event.get("eventType")

    if event_type != "milestone.completed":
        return {"ok": True}

    project_id = event.get("projectId", "")
    milestone_name = event.get("milestoneName", "")

    # Находим сделку по project_id из кастомного поля
    lead_id = find_lead_by_rocketlane_project(project_id)
    if lead_id:
        add_kommo_note(
            lead_id,
            f"Rocketlane: milestone завершён - '{milestone_name}'"
        )

    return {"ok": True}


def find_lead_by_rocketlane_project(project_id: str) -> int | None:
    """Поиск сделки в Kommo по кастомному полю Rocketlane Project ID."""
    ROCKETLANE_FIELD_ID = int(os.environ.get("KOMMO_ROCKETLANE_FIELD_ID", 0))
    if not ROCKETLANE_FIELD_ID:
        return None
    url = f"https://{KOMMO_DOMAIN}/api/v4/leads"
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    params = {f"filter[custom_fields][{ROCKETLANE_FIELD_ID}][]": project_id}
    r = requests.get(url, params=params, headers=headers, timeout=10)
    if r.ok:
        leads = r.json().get("_embedded", {}).get("leads", [])
        return leads[0]["id"] if leads else None
    return None


if __name__ == "__main__":
    app.run(port=5000)

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

Для SaaS-компании с ARR от $500k и 5-10 новых клиентов в месяц автоматизация онбординга через Rocketlane критична для масштабирования CSM-команды.

До интеграции: CSM получал сигнал о новом клиенте из Slack-уведомления от продажника. Создание проекта в Rocketlane занимало 20-40 минут: заполнить данные, выбрать шаблон, добавить клиента, настроить временные рамки. При 8 новых клиентах в месяц - 3-5 часов CSM-времени только на создание проектов.

После интеграции: проект создаётся автоматически в течение 30 секунд после закрытия сделки. Клиент получает приглашение на customer portal через email от Rocketlane ещё до того, как CSM успевает открыть ноутбук. Типичный эффект - сокращение time-to-onboarding с 2-3 дней до нескольких часов.

Дополнительный эффект: CSM видит в Rocketlane все данные клиента (название компании, имя, размер сделки) уже заполненными - не нужно переносить вручную из Kommo.

Для кого подходит

Интеграция актуальна для компаний, которые:

  • Используют Rocketlane для структурированного онбординга клиентов с customer portal
  • Ведут воронку продаж в Kommo и хотят автоматизировать передачу клиента от sales к success
  • Имеют повторяемый процесс онбординга (шаблон в Rocketlane), который применяется к каждому новому клиенту
  • Работают в B2B SaaS или professional services с циклом онбординга от 2 недель

Если вы используете другие task management инструменты - например, Kommo + Asana или Kommo + ClickUp - принцип автоматизации при Won-событии аналогичен.

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

Можно ли использовать разные шаблоны Rocketlane для разных тарифных планов? Да. Добавьте кастомное поле «Тарифный план» в Kommo. В обработчике webhook читайте это поле и выбирайте соответствующий templateId из маппинга. Например: Plan_Basic -> template_001, Plan_Pro -> template_002.

Что если сделка «Won» создана задним числом и онбординг уже начался? Рекомендуется добавить проверку на дубли: перед созданием проекта проверяйте кастомное поле rocketlane_project_id в сделке. Если оно заполнено - проект уже создан, пропускаем.

Можно ли добавить нескольких контактов клиента в Rocketlane? Да. Kommo позволяет привязывать несколько контактов к сделке. В обработчике пройдитесь по всем контактам с типом «клиент» и вызовите add_customer_to_project для каждого.

Нужна ли обратная синхронизация всех задач Rocketlane в Kommo? Не рекомендуется - это создаёт шум в Kommo. Достаточно синхронизировать ключевые milestone (завершение onboarding, первый успех клиента). Мелкие задачи Rocketlane не нужны в CRM.

Если вам нужна интеграция Kommo с Rocketlane - опишите ваш стек и сценарий команде Exceltic.dev. Разберём архитектуру за одну встречу.

Ещё статьи

Все →