Kommo + Concord: автоматическая отправка контрактов
При переходе сделки в «Выиграно» Kommo автоматически создаёт документ из шаблона в Concord CLM, подставляет данные клиента и отправляет на подписание. Ссылка на контракт и его статус возвращаются обратно в карточку CRM.
При закрытии сделки менеджер вручную копирует имя компании, сумму, реквизиты и условия в шаблон контракта в Concord, затем отправляет его контрагенту и ждёт. При 30-50 сделках в месяц это около 3-4 часов ручного заполнения и постоянный источник ошибок: неверная сумма, старая версия шаблона, перепутанные реквизиты. Один такой контракт с ошибкой может задержать закрытие сделки на неделю. Concord - CLM-платформа (Contract Lifecycle Management) с поддержкой шаблонов, переменных и workflow согласования. Её API позволяет создавать документы программно, передавая значения переменных из внешних систем. В статье - архитектура двунаправленной интеграции, Python-код и кейс B2B SaaS-компании.
Почему ручной process документооборота не масштабируется
Kommo не имеет нативной интеграции с Concord. Существующие решения через Zapier упираются в одно ограничение: Zapier умеет триггерить создание документа в Concord, но не умеет корректно маппировать сложные переменные шаблона (вложенные объекты, условные блоки) и не обрабатывает обратный webhook при подписании.
В итоге компании получают полуавтоматику: документ создаётся автоматически, но менеджер всё равно заходит в Concord, проверяет данные, исправляет ошибки маппинга и только потом отправляет. Ценность автоматизации падает до нуля.
Кастомная интеграция через API закрывает весь цикл: создание документа, отправка, отслеживание статуса, запись результата в CRM.
Архитектура интеграции
Двунаправленный поток данных:
Kommo -> Concord (при выигрыше сделки):
- Webhook от Kommo на ваш сервис
- Сервис запрашивает полные данные сделки и контакта через Kommo API
- Создаёт документ из шаблона в Concord через
POST /1/documents - Записывает URL контракта обратно в кастомное поле Kommo
Concord -> Kommo (при изменении статуса контракта):
- Webhook от Concord при
document.signed,document.countersigned,document.expired - Сервис обновляет кастомное поле «Статус контракта» в Kommo
- Добавляет заметку с датой и участниками подписания
Аутентификация Concord API: Bearer token (Authorization: Bearer <api_key>) из раздела Settings -> API в вашем Concord аккаунте.
import httpx
from fastapi import FastAPI, Request, HTTPException
import logging
app = FastAPI()
logger = logging.getLogger(__name__)
CONCORD_API_KEY = "your_concord_api_key"
CONCORD_TEMPLATE_ID = "template_uuid_here" # ID шаблона в Concord
KOMMO_SUBDOMAIN = "your_company"
KOMMO_TOKEN = "your_kommo_token"
# ID кастомных полей в Kommo (узнать через GET /api/v4/leads/custom_fields)
FIELD_CONTRACT_URL = 123456
FIELD_CONTRACT_STATUS = 123457
async def get_kommo_deal(deal_id: int) -> dict:
"""Получает данные сделки и связанного контакта."""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4/leads/{deal_id}",
headers={"Authorization": f"Bearer {KOMMO_TOKEN}"},
params={"with": "contacts"},
timeout=10.0
)
resp.raise_for_status()
return resp.json()
async def get_kommo_contact(contact_id: int) -> dict:
"""Получает данные контакта (email, телефон, компания)."""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4/contacts/{contact_id}",
headers={"Authorization": f"Bearer {KOMMO_TOKEN}"},
timeout=10.0
)
resp.raise_for_status()
return resp.json()
async def create_concord_document(deal_data: dict, contact_data: dict) -> dict:
"""
Создаёт документ из шаблона в Concord.
Маппинг переменных шаблона из полей Kommo.
"""
# Извлекаем нужные поля из данных Kommo
custom_fields = {cf["field_id"]: cf["values"][0]["value"]
for cf in deal_data.get("custom_fields_values", [])}
# Переменные для шаблона Concord
template_variables = {
"client_name": contact_data.get("name", ""),
"company_name": contact_data.get("company", {}).get("name", ""),
"client_email": next(
(v["value"] for v in contact_data.get("custom_fields_values", [])
if v.get("field_type") == "EMAIL"), ""
),
"deal_amount": str(deal_data.get("price", 0)),
"deal_name": deal_data.get("name", ""),
"deal_id": str(deal_data.get("id", "")),
# Дополнительные поля из custom fields Kommo:
"contract_start_date": custom_fields.get(111111, ""),
"subscription_term": custom_fields.get(111112, "12 месяцев"),
}
async with httpx.AsyncClient() as client:
resp = await client.post(
"https://api.concord.app/1/documents",
headers={
"Authorization": f"Bearer {CONCORD_API_KEY}",
"Content-Type": "application/json"
},
json={
"template_id": CONCORD_TEMPLATE_ID,
"title": f"Contract - {deal_data.get('name', '')} #{deal_data.get('id')}",
"variables": template_variables,
"send_for_signature": True # автоматически отправить на подписание
},
timeout=15.0
)
resp.raise_for_status()
return resp.json()
async def update_kommo_deal_fields(deal_id: int, contract_url: str, status: str):
"""Обновляет кастомные поля сделки Kommo."""
async with httpx.AsyncClient() as client:
await client.patch(
f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4/leads/{deal_id}",
headers={"Authorization": f"Bearer {KOMMO_TOKEN}"},
json={
"custom_fields_values": [
{"field_id": FIELD_CONTRACT_URL, "values": [{"value": contract_url}]},
{"field_id": FIELD_CONTRACT_STATUS, "values": [{"value": status}]}
]
},
timeout=10.0
)
@app.post("/kommo/webhook")
async def handle_kommo_webhook(request: Request):
"""Обрабатывает переход сделки в Won, создаёт контракт в Concord."""
data = await request.json()
for lead in data.get("leads", {}).get("status", []):
if lead.get("status_id") == 142: # Won
deal_id = lead["id"]
deal_data = await get_kommo_deal(deal_id)
# Берём первый связанный контакт
contact_id = deal_data["_embedded"]["contacts"][0]["id"]
contact_data = await get_kommo_contact(contact_id)
doc = await create_concord_document(deal_data, contact_data)
contract_url = doc.get("document_url", "")
await update_kommo_deal_fields(deal_id, contract_url, "Sent")
logger.info(f"Contract created for deal {deal_id}: {contract_url}")
return {"status": "ok"}
@app.post("/concord/webhook")
async def handle_concord_webhook(request: Request):
"""Обрабатывает события подписания контракта из Concord."""
event = await request.json()
event_type = event.get("event")
document = event.get("document", {})
# Извлекаем deal_id из переменных документа
variables = document.get("variables", {})
deal_id_str = variables.get("deal_id")
if not deal_id_str:
return {"status": "skipped"}
deal_id = int(deal_id_str)
status_map = {
"document.signed": "Signed",
"document.countersigned": "Countersigned",
"document.expired": "Expired",
}
new_status = status_map.get(event_type, "Unknown")
contract_url = document.get("document_url", "")
await update_kommo_deal_fields(deal_id, contract_url, new_status)
return {"status": "ok"}
Пошаговая реализация
Шаг 1. Подготовка шаблона в Concord
В Concord создайте или адаптируйте шаблон контракта. Переменные задаются двойными фигурными скобками: {{client_name}}, {{deal_amount}}, {{contract_start_date}}. Убедитесь что шаблон работает при ручном тесте. Сохраните UUID шаблона из URL или через API.
Шаг 2. Маппинг полей Kommo
Определите, какие поля сделки и контакта нужны для заполнения шаблона. Получите список кастомных полей через GET /api/v4/leads/custom_fields. Стандартные поля сделки (name, price, status) доступны напрямую.
Шаг 3. Webhook из Kommo
Настройте webhook в Kommo на событие смены этапа воронки. Используйте конкретный pipeline_id и status_id этапа «Выиграно», чтобы не обрабатывать лишние события.
Шаг 4. Webhook в Concord
По документации Concord настройте webhook на события документа. Укажите URL вашего сервиса и подпишитесь на document.signed, document.countersigned, document.expired.
Шаг 5. Хранение deal_id в переменных документа
Ключевой паттерн: передавайте deal_id как переменную шаблона (даже если она не отображается в тексте контракта). Это позволяет в обратном webhook точно определить, какую сделку обновлять.
Шаг 6. Обработка ошибок
Concord API может вернуть 422 если переменная шаблона не заполнена или шаблон не найден. Логируйте детали ошибки и отправляйте уведомление в Slack/email - это позволит быстро исправить маппинг без потери сделки.
Реальный кейс: B2B SaaS, 35 сделок в месяц
Клиент - B2B SaaS-компания с командой продаж 8 человек в Европе. До интеграции: AE закрывает звонок, переключается в Concord, вручную создаёт документ из шаблона, заполняет 7-8 переменных, проверяет данные, отправляет. Среднее время - 15-20 минут на контракт. Ошибки (неверная сумма, старая версия шаблона) - 2-3 в месяц.
После интеграции: при смене этапа в Kommo контракт создаётся и уходит клиенту автоматически за 5-10 секунд. AE видит ссылку в карточке, статус «Sent» меняется на «Signed» автоматически.
Результат:
- Экономия: ~9 часов в месяц (35 сделок x 15 мин)
- Ошибки данных в контрактах: снизились до нуля
- Скорость подписания выросла: клиент получает контракт пока настроение позитивное, а не через час
- Руководитель видит в Kommo список сделок с неподписанными контрактами - раньше это требовало ручной сверки
Компания дополнительно настроила триггер: если контракт не подписан за 48 часов, Kommo автоматически создаёт задачу для менеджера с пометкой «follow up».
Для кого подходит эта интеграция
- Компании с 20+ сделками в месяц, где каждая требует контракта
- B2B SaaS, агентства, консалтинг - где шаблон контракта стандартизирован
- Команды, где AE закрывает сделку и сам же занимается документооборотом
- Ситуации с несколькими подписантами: Concord поддерживает последовательное и параллельное подписание
Если вы уже используете DocuSign или Dropbox Sign, Concord предлагает схожую функциональность с фокусом на управление жизненным циклом контрактов и встроенные инструменты согласования.
Часто задаваемые вопросы
Concord поддерживает юридически значимую подпись в EU (eIDAS)?
Concord предоставляет электронную подпись, соответствующую требованиям eIDAS в категории Simple Electronic Signature (SES). Для большинства B2B-контрактов этого достаточно. Если вам нужна Advanced или Qualified Electronic Signature, рассмотрите специализированные EU-платформы (Yousign, Docuseal с EU Cloud). Проверяйте текущий статус соответствия в документации Concord перед финальным выбором.
Как передать в контракт данные из нескольких контактов (например, два подписанта)?
Concord API позволяет указать список подписантов с email-адресами при создании документа через поле signers. В Kommo создайте кастомные поля для второго контакта-подписанта и передавайте их в теле запроса к API. Порядок подписания (последовательный или параллельный) настраивается через параметр signing_order.
Что если сделка была переведена в Won по ошибке и нужно отозвать контракт?
Concord позволяет отозвать документ через API (DELETE /1/documents/{id} или через dashboard). Рекомендуем добавить задержку 2-5 минут между триггером в Kommo и созданием контракта - это даст менеджеру время заметить ошибку. Также можно добавить кастомное поле-флаг «Не создавать контракт автоматически» для исключительных случаев.
Можно ли использовать разные шаблоны для разных типов сделок?
Да. В Kommo создайте кастомное поле «Тип контракта» или используйте название воронки / этапа для выбора нужного template_id в Concord. Логика выбора шаблона реализуется на стороне вашего webhook-сервиса простым словарём {deal_type: template_uuid}.
Как протестировать интеграцию без отправки реальных контрактов клиентам?
Concord поддерживает тестовый режим через sandbox-аккаунт. В Kommo создайте тестовую воронку или тестовый этап для QA. Параметр send_for_signature: false позволяет создать документ без немедленной отправки - полезно для проверки маппинга данных.
Если ваша команда тратит 10+ часов в месяц на заполнение контрактов вручную - опишите задачу команде Exceltic.dev. Это типовая задача для нас: разберём ваш стек, маппинг шаблонов и предложим решение.