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
| Статус | Что означает |
|---|---|
| CREATED | Workflow только создан |
| 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. Разберём архитектуру под ваш стек.