Kommo + Ironclad: запуск контракта из CRM-воронки без ручной работы

Ironclad - CLM-платформа (Contract Lifecycle Management) с API для создания и управления контрактами программно. Интеграция с Kommo решает типовую проблему: когда сделка достигает этапа переговоров, нужно запустить контракт с нужными параметрами - именем контрагента, суммой, датой. Вместо ручного перехода в Ironclad и заполнения формы - один автоматический вызов при смене этапа в CRM.

Ironclad API использует Bearer token (API Key из Ironclad Admin -> Integrations -> API). Основные эндпоинты: GET /v1/templates - список шаблонов workflow, POST /v1/workflows - запустить новый контракт, GET /v1/workflows/{workflowId} - статус. Webhook workflow.state.changed уведомляет когда контракт подписан (Executed) - и в этот момент Kommo переходит в Closed Won автоматически.

CLM (Contract Lifecycle Management) - класс инструментов для управления всем жизненным циклом контракта: от создания и согласования до подписания, хранения и продления. Ironclad - один из лидеров сегмента, позиционируется как enterprise-решение с workflow-движком поверх простого eSign.

В чём проблема с нативной интеграцией

У Ironclad нет нативного коннектора к Kommo. Типовой обходной путь - Zapier - не работает: у Ironclad ограниченный Zapier-коннектор без поддержки signerGroups и attributes, которые обязательны для корректного запуска workflow. Без этих параметров контракт создаётся пустым шаблоном без данных сделки.

Помимо этого, нет обратной связи: даже если удастся создать workflow через Zapier, событие “контракт подписан” не доходит до Kommo - менеджер не знает, когда закрывать сделку.

Архитектура

Kommo: сделка -> этап "Отправить контракт"
  -> Kommo webhook leads.status.changed
  -> Ваш сервер

Ваш сервер:
  -> GET /v1/templates -> найти template по имени
  -> GET Kommo: имя контрагента, сумма, email подписанта
  -> POST /v1/workflows -> workflowId
  -> Kommo: note с workflowId и ссылкой

Ironclad workflow: Draft -> Review -> Signature -> Executed
  -> webhook workflow.state.changed (state = "EXECUTED")
  -> Ваш сервер: найти сделку по workflowId
  -> Kommo: PATCH leads -> Closed Won

Запуск workflow

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

app = Flask(__name__)

IRONCLAD_KEY    = os.environ["IRONCLAD_API_KEY"]
IRONCLAD_BASE   = "https://ironcladapp.com/public/api"
IRONCLAD_HDR    = {"Authorization": f"Bearer {IRONCLAD_KEY}",
                   "Content-Type": "application/json"}

KOMMO_SUBDOMAIN    = os.environ["KOMMO_SUBDOMAIN"]
KOMMO_TOKEN        = os.environ["KOMMO_ACCESS_TOKEN"]
CONTRACT_STAGE_ID  = int(os.environ["KOMMO_CONTRACT_STAGE_ID"])
CLOSED_WON_ID      = int(os.environ["KOMMO_CLOSED_WON_ID"])
TEMPLATE_NAME      = os.environ.get("IRONCLAD_TEMPLATE_NAME", "MSA")

KOMMO_BASE = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4"
KOMMO_HDR  = {"Authorization": f"Bearer {KOMMO_TOKEN}",
              "Content-Type": "application/json"}

# Для хранения mapping workflowId -> leadId (в production используйте БД или Redis)
workflow_to_lead: dict = {}

def get_template_id(name: str) -> str:
    r = requests.get(f"{IRONCLAD_BASE}/v1/templates", headers=IRONCLAD_HDR)
    r.raise_for_status()
    for t in r.json().get("templates", []):
        if name.lower() in t.get("name", "").lower():
            return t["id"]
    raise ValueError(f"Template '{name}' not found")

def launch_workflow(template_id: str,
                    counterparty: str, amount: float,
                    signer_email: str, signer_name: str) -> dict:
    r = requests.post(
        f"{IRONCLAD_BASE}/v1/workflows",
        headers=IRONCLAD_HDR,
        json={
            "template": {"id": template_id},
            "attributes": {
                "counterpartyName": counterparty,
                "contractValue":    amount,
            },
            "signerGroups": [
                {
                    "group": "1",
                    "signers": [
                        {"email": signer_name, "name": signer_name}
                    ],
                }
            ],
        },
    )
    r.raise_for_status()
    return r.json()

def get_lead_contact(lead_id: int) -> tuple:
    r = requests.get(
        f"{KOMMO_BASE}/leads/{lead_id}",
        headers=KOMMO_HDR,
        params={"with": "contacts"},
    )
    lead = r.json()
    contacts = lead.get("_embedded", {}).get("contacts", [])
    contact = {}
    if contacts:
        rc = requests.get(
            f"{KOMMO_BASE}/contacts/{contacts[0]['id']}",
            headers=KOMMO_HDR,
            params={"with": "custom_fields_values"},
        )
        contact = rc.json()
    return lead, contact

def get_email(contact: dict) -> str:
    for cf in contact.get("custom_fields_values", []) or []:
        if cf.get("field_code") == "EMAIL":
            vals = cf.get("values", [])
            return vals[0].get("value", "") if vals else ""
    return ""

def add_note(lead_id: int, text: str):
    requests.post(
        f"{KOMMO_BASE}/notes",
        headers=KOMMO_HDR,
        json=[{"entity_id": lead_id, "entity_type": "leads",
               "note_type": "common", "params": {"text": text}}],
    )

@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
    data = request.json or {}
    for lead_data in data.get("leads", {}).get("status", []):
        lead_id    = lead_data.get("id")
        new_status = lead_data.get("status_id")
        if new_status != CONTRACT_STAGE_ID:
            continue

        lead, contact = get_lead_contact(lead_id)
        amount      = float(lead.get("price") or 0)
        company     = contact.get("name", f"Deal #{lead_id}")
        email       = get_email(contact)
        signer_name = contact.get("name", "")

        if not email:
            add_note(lead_id, "Ironclad: email подписанта не указан, запустите контракт вручную.")
            continue

        template_id = get_template_id(TEMPLATE_NAME)
        wf          = launch_workflow(template_id, company, amount, email, signer_name)
        wf_id       = wf.get("id", "")

        workflow_to_lead[wf_id] = lead_id
        view_url = wf.get("viewerUrl", "")
        add_note(lead_id,
                 f"Ironclad workflow #{wf_id} запущен. Статус: Draft.\n{view_url}")

    return jsonify({"status": "ok"}), 200

Webhook при подписании контракта

@app.route("/webhooks/ironclad", methods=["POST"])
def ironclad_webhook():
    event = request.json or {}
    if event.get("event") != "workflow.state.changed":
        return jsonify({"status": "ignored"}), 200

    wf_id    = event.get("workflowId", "")
    new_state = event.get("workflowStatus", "")

    if new_state != "EXECUTED":
        return jsonify({"status": "not_executed"}), 200

    lead_id = workflow_to_lead.get(wf_id)
    if not lead_id:
        return jsonify({"status": "no_lead"}), 200

    requests.patch(
        f"{KOMMO_BASE}/leads/{lead_id}",
        headers=KOMMO_HDR,
        json={"status_id": CLOSED_WON_ID},
    )
    add_note(lead_id, f"Ironclad контракт #{wf_id} подписан (Executed). Сделка закрыта.")
    del workflow_to_lead[wf_id]
    return jsonify({"status": "ok"}), 200

Настройка webhook в Ironclad

В Ironclad Admin -> Integrations -> Webhooks: добавьте URL https://your-server.com/webhooks/ironclad, выберите событие workflow.state.changed. Ironclad подписывает запросы через HMAC-SHA256 (секрет из настроек) в заголовке X-Ironclad-Signature.

Для production: добавьте верификацию подписи - это стандартный HMAC-SHA256 от body с ключом из настроек webhook.

Состояния workflow в Ironclad

СтатусЧто означает
CREATEDWorkflow только создан
DRAFTКоманда заполняет поля
REVIEWКонтракт на юридическом согласовании
SIGNATUREОтправлен подписантам
EXECUTEDВсе подписали
CANCELLEDОтменён

Для большинства sales-кейсов интересны SIGNATURE (напомнить менеджеру что ждём подписи) и EXECUTED (закрыть сделку в CRM).

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

B2B SaaS компания с enterprise-клиентами: средний цикл сделки 45 дней, контракт согласовывается 7-14 дней. До интеграции: менеджер вручную запускал Ironclad workflow и через 2 недели - вручную закрывал сделку в Kommo. После: workflow запускается автоматически при смене этапа, CRM обновляется при подписании. Экономия - 15 минут на сделку, 0 забытых “закрыть сделку после подписания”.

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

B2B компании с enterprise-клиентами и юридическим согласованием контрактов. Особенно релевантно для SaaS с MSA/NDA flow, профессиональных услуг с Statement of Work, и любого бизнеса где между “договорились” и “подписали” проходит 2+ недели согласований. Kommo + Ironclad закрывает gap между CRM-процессом продаж и CLM-процессом юридического оформления.

Другие интеграции документооборота: Kommo + Skribble (eIDAS/QES подпись), Kommo + Documenso (open source eSign).

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

Поддерживает ли Ironclad API версионирование контрактов?

Да. Каждое изменение контракта создаёт новую версию. Через API можно получить историю версий: GET /v1/workflows/{workflowId}/revisions. В webhook workflow.state.changed приходит актуальная версия.

Можно ли получить подписанный PDF через API?

Да: GET /v1/workflows/{workflowId}/documents возвращает список документов, каждый с downloadUrl. PDF доступен после перехода в EXECUTED.

Как передать custom поля из Kommo в Ironclad?

Через attributes в теле запроса POST /v1/workflows. Ключи должны совпадать с именами полей в Ironclad template. Узнать допустимые ключи: GET /v1/templates/{templateId} - вернёт список schemaFields с типами и именами.

Есть ли у Ironclad sandbox для тестирования?

Да: sandbox.ironcladapp.com. Создайте отдельный API-ключ для sandbox в Ironclad Admin -> Integrations -> API. Webhook-события из sandbox приходят так же, как из production.

Итог

Kommo + Ironclad CLM - автоматический контрактный flow:

  • Bearer token, POST /v1/workflows с template, attributes, signerGroups
  • Хранить mapping workflowId -> leadId (in-memory или Redis)
  • Webhook workflow.state.changed, state EXECUTED -> Kommo Closed Won
  • Статусы: CREATED -> DRAFT -> REVIEW -> SIGNATURE -> EXECUTED
  • Sandbox: sandbox.ironcladapp.com для тестирования без реальных контрактов

Если ваша команда использует Ironclad для enterprise-контрактов и хочет автоматизировать flow с Kommo - опишите задачу команде Exceltic.dev. Разберём архитектуру под ваш стек.

Ещё статьи

Все →