DocuSign - глобальный лидер рынка электронной подписи с долей более 70% среди B2B-компаний в Северной Америке. Kommo - CRM для управления воронкой продаж. Без интеграции между ними юридически значимый момент - подписание контракта - не отражается в CRM автоматически. Менеджер получает email от DocuSign «Contract signed», вручную открывает Kommo и переводит сделку на следующий этап.
Эта задержка и ручной шаг создают несколько проблем: сделки зависают в статусе «Контракт отправлен» даже после подписания, отчётность не отражает реального положения дел, онбординг нового клиента начинается с опозданием. Для команд с 30+ активными сделками и несколькими менеджерами это системная проблема, а не случайный сбой.
В этой статье покажем, как связать DocuSign с Kommo через DocuSign Connect (webhook), передавать ID сделки в конверт и автоматически двигать воронку при каждом событии подписания.
Почему стандартные инструменты не работают
DocuSign имеет Zapier-интеграцию, которая ловит envelope.completed. Но у неё два ограничения: нет встроенного способа передать и получить обратно произвольный идентификатор Kommo, и задержка Zapier (до 15 минут в зависимости от тарифа) критична когда клиент подписал - а ваш менеджер уже должен звонить с онбордингом.
Zoho Sign, Scrive и другие e-sign платформы используют аналогичную архитектуру. Основной принцип для любой из них: передавать kommo_lead_id в метаданные конверта при создании, чтобы webhook однозначно знал какую сделку обновлять.
Ключевой механизм: Custom Fields в конверте
DocuSign поддерживает Custom Fields - произвольные поля на уровне envelope. Они передаются при создании конверта через API и возвращаются в каждом webhook-событии. Это точка привязки: кладём kommo_lead_id в custom field при создании конверта, и webhook всегда знает, какую сделку обновлять.
import docusign_esign as dse
# Создание конверта с привязкой к Kommo lead ID
def create_docusign_envelope(lead_id: str, signer_email: str,
signer_name: str, doc_base64: str) -> str:
api_client = get_ds_api_client() # JWT Auth
envelopes_api = dse.EnvelopesApi(api_client)
document = dse.Document(
document_base64=doc_base64,
name="Contract",
file_extension="pdf",
document_id="1"
)
signer = dse.Signer(
email=signer_email,
name=signer_name,
recipient_id="1",
routing_order="1"
)
# Sign here tab
sign_here = dse.SignHere(
anchor_string="/sn1/",
anchor_units="pixels",
anchor_y_offset="10",
anchor_x_offset="20"
)
signer.tabs = dse.Tabs(sign_here_tabs=[sign_here])
# Custom field с kommo_lead_id
custom_field = dse.TextCustomField(
name="kommo_lead_id",
value=str(lead_id),
required="false",
show="false" # скрыто от подписанта
)
envelope_def = dse.EnvelopeDefinition(
email_subject="Контракт для подписания",
documents=[document],
recipients=dse.Recipients(signers=[signer]),
custom_fields=dse.CustomFields(text_custom_fields=[custom_field]),
status="sent"
)
result = envelopes_api.create_envelope(
account_id=DS_ACCOUNT_ID,
envelope_definition=envelope_def
)
return result.envelope_id
Настройка DocuSign Connect
DocuSign Connect - встроенная webhook-система. Настраивается в DocuSign Admin -> Settings -> Connect.
Параметры конфигурации:
- URL: ваш endpoint (
https://your-server.com/docusign/webhook) - Trigger Events:
Envelope Completed,Envelope Voided,Recipient Completed - Include Data: выбрать
Custom Fields,Recipients,Envelope Fields - Authentication: HMAC (рекомендуется) или Basic Auth
Обработка webhook-событий
from flask import Flask, request, abort
import requests, hashlib, hmac, base64
app = Flask(__name__)
DS_HMAC_KEY = "your_docusign_hmac_key" # из Connect конфигурации
KOMMO_DOMAIN = "yourdomain.kommo.com"
KOMMO_TOKEN = "your_kommo_token"
KOMMO_BASE = f"https://{KOMMO_DOMAIN}/api/v4"
def verify_docusign_hmac(payload: bytes, signature_b64: str) -> bool:
"""DocuSign HMAC-SHA256 verification."""
mac = hmac.new(DS_HMAC_KEY.encode(), payload, hashlib.sha256)
expected = base64.b64encode(mac.digest()).decode()
return hmac.compare_digest(expected, signature_b64)
@app.route("/docusign/webhook", methods=["POST"])
def docusign_webhook():
sig = request.headers.get("X-DocuSign-Signature-1", "")
if not verify_docusign_hmac(request.get_data(), sig):
abort(401)
event = request.json
status = event.get("status", "")
# Извлекаем kommo_lead_id из custom fields
lead_id = None
custom_fields = (
event.get("envelopeSummary", {})
.get("customFields", {})
.get("textCustomFields", [])
)
for field in custom_fields:
if field.get("name") == "kommo_lead_id":
lead_id = field.get("value")
break
if not lead_id:
return "ok", 200 # конверт не связан с Kommo
envelope_id = event.get("envelopeId", "")
completed_time = event.get("completedDateTime", "")[:10]
if status == "completed":
handle_signed(lead_id, envelope_id, completed_time)
elif status == "voided":
void_reason = event.get("voidedReason", "")
handle_voided(lead_id, envelope_id, void_reason)
return "ok", 200
def get_kommo_headers():
return {
"Authorization": f"Bearer {KOMMO_TOKEN}",
"Content-Type": "application/json"
}
def handle_signed(lead_id: str, envelope_id: str, signed_date: str):
"""Envelope completed: advance stage, save envelope ID."""
hs = requests.Session()
hs.headers.update(get_kommo_headers())
# Двигаем сделку на следующий этап + кастомные поля
hs.patch(f"{KOMMO_BASE}/leads", json=[{
"id": int(lead_id),
"pipeline_id": YOUR_PIPELINE_ID,
"status_id": CONTRACT_SIGNED_STAGE_ID, # этап «Контракт подписан»
"custom_fields_values": [
{"field_code": "DOCUSIGN_ENVELOPE_ID",
"values": [{"value": envelope_id}]},
{"field_code": "CONTRACT_SIGNED_DATE",
"values": [{"value": signed_date}]},
]
}])
# Заметка с подтверждением
hs.post(f"{KOMMO_BASE}/leads/notes", json=[{
"entity_id": int(lead_id),
"note_type": "common",
"params": {"text": (
f"Контракт DocuSign подписан {signed_date}.\n"
f"Envelope ID: {envelope_id}"
)}
}])
def handle_voided(lead_id: str, envelope_id: str, reason: str):
"""Envelope voided: create task for manager."""
hs = requests.Session()
hs.headers.update(get_kommo_headers())
import time
hs.post(f"{KOMMO_BASE}/tasks", json=[{
"task_type_id": 2, # встреча/задача
"entity_type": "leads",
"entity_id": int(lead_id),
"text": f"Конверт DocuSign отозван. Причина: {reason}. Проверьте контракт и отправьте повторно.",
"complete_till": int(time.time()) + 86400,
}])
hs.post(f"{KOMMO_BASE}/leads/notes", json=[{
"entity_id": int(lead_id),
"note_type": "common",
"params": {"text": f"Конверт DocuSign аннулирован. Причина: {reason}. Envelope ID: {envelope_id}"}
}])
CONTRACT_SIGNED_STAGE_ID и YOUR_PIPELINE_ID - значения из настроек вашей воронки в Kommo. Они доступны через GET /api/v4/leads/pipelines.
JWT-аутентификация для DocuSign API
Для создания конвертов через DocuSign API (а не только приёма webhook) используется JWT Grant Flow:
from docusign_esign import ApiClient
from jwt import encode as jwt_encode
import time
DS_INTEGRATION_KEY = "your_integration_key"
DS_ACCOUNT_ID = "your_account_id"
DS_PRIVATE_KEY = open("docusign_private_key.pem").read()
DS_USER_ID = "your_user_id"
def get_ds_api_client() -> ApiClient:
api_client = ApiClient()
api_client.set_base_path("https://na4.docusign.net/restapi")
jwt_payload = {
"iss": DS_INTEGRATION_KEY,
"sub": DS_USER_ID,
"aud": "account-d.docusign.com",
"iat": int(time.time()),
"exp": int(time.time()) + 3600,
"scope": "signature impersonation"
}
token = jwt_encode(jwt_payload, DS_PRIVATE_KEY, algorithm="RS256")
api_client.set_default_header("Authorization", f"Bearer {token}")
return api_client
Private key генерируется в DocuSign Admin -> Apps and Keys -> RSA Keypairs. Access нужно явно подтвердить один раз через admin consent URL.
Реальный кейс
SaaS-компания с enterprise-продажами: средний цикл 45 дней, 3-4 этапа включая подписание MSA и Order Form. До интеграции: после подписания конверт приходил на email менеджера, тот открывал Kommo и вручную обновлял сделку. Среднее время задержки - 2-4 часа. В 12% случаев менеджер забывал обновить сделку в тот же день.
После кастомной интеграции:
- Сделка переходит на этап «Контракт подписан» в течение 30 секунд
- Дата подписания и Envelope ID записываются в поля Kommo автоматически
- При аннулировании конверта - задача менеджеру создаётся немедленно
- 0 «потерянных» сделок со статусом «Ждём подписания» после фактического подписания
Для кого актуально
B2B-команды на Kommo с циклом продаж, включающим обязательное подписание контракта. Особенно важно для enterprise-сегмента, где контракт подписывают несколько сторон (несколько подписантов в DocuSign) и задержка между последней подписью и началом работ критична.
Аналогичные интеграции реализованы для других e-sign платформ в нашем стеке: Kommo + Zoho Sign, Kommo + Scrive, Kommo + Juro - последний актуален если нужно не просто подписание, а AI-генерация самого контракта.
Часто задаваемые вопросы
Работает ли это с DocuSign Free план?
DocuSign Connect (webhook) доступен на планах Business Pro и выше. На базовом Personal план webhooks недоступны. Для API-интеграции нужен Business Pro или Enterprise. Проверьте ваш план в DocuSign Admin -> Billing.
Как обработать конверт с несколькими подписантами?
Pри конверте с двумя подписантами DocuSign отправляет события recipient_completed для каждого подписанта и финальный envelope_completed когда все подписали. В нашей реализации обрабатываем только envelope_completed - он гарантирует что все подписи получены. Можно добавить промежуточный статус в Kommo при каждом recipient_completed.
Как скачать подписанный PDF и сохранить в Kommo?
DocuSign API позволяет скачать подписанный документ: GET /envelopes/{envelopeId}/documents/combined. В нашей реализации в Kommo записывается ссылка на DocuSign документ, а не сам PDF - чтобы не нагружать Kommo binary-вложениями. Если нужно хранить в Kommo - скачиваем PDF и загружаем через POST /api/v4/leads/{id}/files.
Как передавать kommo_lead_id если конверты создаются вручную в DocuSign Web?
Если менеджеры создают конверты через DocuSign UI (не через API), custom_field с kommo_lead_id можно добавить вручную при создании конверта - DocuSign Web поддерживает custom fields в Advanced Options. Альтернатива: идентифицировать сделку по email подписанта через Kommo API поиск.
Итог
Kommo + DocuSign интеграция строится на двух точках: передача kommo_lead_id в custom field конверта при создании и обработка envelope.completed / envelope.voided через DocuSign Connect webhook. Схема:
- Создание конверта через DocuSign API с
textCustomField: {name: "kommo_lead_id"} - DocuSign Connect webhook -> извлечение
kommo_lead_idиз custom fields envelope.completed-> Kommo: сделка на этап «Подписан», поля с датой и IDenvelope.voided-> Kommo: задача менеджеру для повторной отправки
Если вы используете DocuSign и хотите убрать ручной шаг между «контракт подписан» и «сделка продвинута» - опишите задачу команде Exceltic.dev. Настроим под вашу структуру воронки и количество подписантов.