Kommo + DocuSign: автоматическое продвижение сделки после подписания контракта

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: сделка на этап «Подписан», поля с датой и ID
  • envelope.voided -> Kommo: задача менеджеру для повторной отправки

Если вы используете DocuSign и хотите убрать ручной шаг между «контракт подписан» и «сделка продвинута» - опишите задачу команде Exceltic.dev. Настроим под вашу структуру воронки и количество подписантов.

Ещё статьи

Все →