Kommo + Scrive: электронная подпись документов из воронки продаж

Почему нативная интеграция не работает

Scrive - шведская платформа электронной подписи, широко используемая в Nordics (Швеция, Норвегия, Дания, Финляндия) и DACH-регионе. В отличие от DocuSign и Adobe Sign, Scrive изначально разрабатывался под требования европейского регулирования eIDAS, что делает его предпочтительным выбором для EU-компаний, которым важна юридическая сила электронной подписи в европейских судах.

Готовой интеграции Scrive + Kommo не существует. Scrive не представлен в маркетплейсе Kommo. Типичный сценарий без интеграции выглядит так: менеджер получает уведомление «сделка перешла на этап Договор», вручную открывает Scrive, создаёт документ, загружает шаблон, вводит email контакта, отправляет. После подписи возвращается в Kommo, меняет этап вручную, прикрепляет PDF. Каждый цикл - 10-15 минут ручной работы.

Для компаний с кастомными интеграциями Kommo это решаемая задача через REST API обеих систем.

Что реализуется - архитектура решения

Bidirectional flow через webhook:

Kommo: смена этапа "Договор"
    --> Webhook --> Python сервис
        --> Scrive API: создать документ, загрузить PDF шаблон
        --> Scrive API: отправить signing invitations

Scrive: document.signed
    --> Webhook --> Python сервис
        --> Kommo API: обновить этап сделки
        --> Kommo API: прикрепить подписанный PDF

Технические детали

Scrive API Auth. Bearer token. Получается через POST /api/v2/oauth/token с client_id и client_secret (из Scrive Admin Panel). Токен долгоживущий, но рекомендуется refresh при ошибке 401.

Ключевые эндпоинты Scrive API v2:

  • POST /api/v2/documents - создать документ
  • POST /api/v2/documents/{id}/files/main - загрузить PDF
  • POST /api/v2/documents/{id}/start - запустить процесс подписания (отправить приглашения)
  • GET /api/v2/documents/{id}/files/sealed - скачать подписанный PDF
  • POST /api/v2/hooks - создать webhook для событий документа

Структура документа Scrive. При создании документа через API передаётся JSON с описанием участников (parties) и полей для заполнения. Каждый party имеет role: signing_party (подписант) или viewer. Поля документа (signatory fields) могут быть signature, text, checkbox, date - они привязываются к координатам на странице PDF.

Kommo Webhooks. При переходе сделки на этап «Договор» Kommo отправляет webhook с leads[status][0][id] (ID сделки) и leads[status][0][status_id] (новый статус). Настройка: Kommo Admin -> Webhooks.

Пошаговая реализация

Шаг 1. Настройка Kommo Webhook

В Kommo Admin Panel -> Интеграции -> Webhooks добавьте URL вашего сервиса. Выберите событие «Смена этапа сделки». Запишите секретный ключ для верификации.

Шаг 2. Создание документа и отправка на подпись

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

app = Flask(__name__)

SCRIVE_BASE = "https://api.scrive.com"
SCRIVE_TOKEN = os.environ["SCRIVE_BEARER_TOKEN"]
KOMMO_DOMAIN = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]

# ID этапа "Договор" в вашей воронке Kommo
CONTRACT_STAGE_ID = int(os.environ.get("KOMMO_CONTRACT_STAGE_ID", 0))
# ID этапа "Договор подписан"
SIGNED_STAGE_ID = int(os.environ.get("KOMMO_SIGNED_STAGE_ID", 0))
# Путь к PDF-шаблону договора
CONTRACT_TEMPLATE_PATH = os.environ.get("CONTRACT_TEMPLATE_PATH", "contract_template.pdf")


def get_lead_details(lead_id: int) -> dict:
    """Получаем данные сделки из Kommo."""
    url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}"
    params = {"with": "contacts"}
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    r = requests.get(url, params=params, headers=headers, timeout=10)
    r.raise_for_status()
    return r.json()


def get_contact_details(contact_id: int) -> dict:
    """Получаем данные контакта."""
    url = f"https://{KOMMO_DOMAIN}/api/v4/contacts/{contact_id}"
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    r = requests.get(url, headers=headers, timeout=10)
    r.raise_for_status()
    return r.json()


def create_scrive_document(title: str, signer_email: str, signer_name: str) -> dict:
    """Создаём документ в Scrive и загружаем PDF шаблон."""
    scrive_headers = {
        "Authorization": f"Bearer {SCRIVE_TOKEN}",
        "Content-Type": "application/json",
    }

    # Создаём документ
    doc_payload = {
        "title": title,
        "parties": [
            {
                "role": "signing_party",
                "is_author": False,
                "delivery_method": "email",
                "fields": [
                    {"type": "email", "value": signer_email},
                    {"type": "name", "value": signer_name},
                    {
                        "type": "signature",
                        "name": "client_signature",
                        "placements": [
                            {"xrel": 0.1, "yrel": 0.85, "wrel": 0.3, "hrel": 0.05, "page": 1}
                        ]
                    }
                ]
            },
            {
                "role": "signing_party",
                "is_author": True,
                "delivery_method": "email",
            }
        ],
        "lang": "en",
        "is_template": False,
    }

    r = requests.post(
        f"{SCRIVE_BASE}/api/v2/documents",
        json=doc_payload,
        headers=scrive_headers,
        timeout=30,
    )
    r.raise_for_status()
    doc = r.json()
    doc_id = doc["id"]

    # Загружаем PDF шаблон
    with open(CONTRACT_TEMPLATE_PATH, "rb") as pdf_file:
        upload_r = requests.post(
            f"{SCRIVE_BASE}/api/v2/documents/{doc_id}/files/main",
            files={"file": ("contract.pdf", pdf_file, "application/pdf")},
            headers={"Authorization": f"Bearer {SCRIVE_TOKEN}"},
            timeout=60,
        )
        upload_r.raise_for_status()

    # Запускаем процесс подписания
    start_r = requests.post(
        f"{SCRIVE_BASE}/api/v2/documents/{doc_id}/start",
        headers=scrive_headers,
        timeout=30,
    )
    start_r.raise_for_status()

    return doc


def save_scrive_doc_to_kommo(lead_id: int, scrive_doc_id: str):
    """Сохраняем ID Scrive документа в кастомное поле сделки Kommo."""
    url = f"https://{KOMMO_DOMAIN}/api/v4/leads"
    headers = {
        "Authorization": f"Bearer {KOMMO_TOKEN}",
        "Content-Type": "application/json",
    }
    SCRIVE_DOC_FIELD_ID = int(os.environ.get("KOMMO_SCRIVE_DOC_FIELD_ID", 0))
    payload = [{
        "id": lead_id,
        "custom_fields_values": [
            {"field_id": SCRIVE_DOC_FIELD_ID, "values": [{"value": scrive_doc_id}]}
        ]
    }]
    requests.patch(url, json=payload, headers=headers, timeout=10)


@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
    """Обрабатываем webhook Kommo при смене этапа."""
    data = request.form
    lead_id = int(data.get("leads[status][0][id]", 0))
    new_status_id = int(data.get("leads[status][0][status_id]", 0))

    if lead_id and new_status_id == CONTRACT_STAGE_ID:
        # Сделка перешла на этап "Договор"
        lead = get_lead_details(lead_id)
        contacts = lead.get("_embedded", {}).get("contacts", [])

        if contacts:
            contact_id = contacts[0]["id"]
            contact = get_contact_details(contact_id)

            # Находим email контакта
            signer_email = ""
            signer_name = contact.get("name", "")
            for cf in contact.get("custom_fields_values", []) or []:
                if cf.get("field_code") == "EMAIL":
                    signer_email = cf["values"][0]["value"]
                    break

            if signer_email:
                doc_title = f"Договор - {lead.get('name', 'Сделка')} #{lead_id}"
                doc = create_scrive_document(doc_title, signer_email, signer_name)
                save_scrive_doc_to_kommo(lead_id, doc["id"])

    return {"ok": True}

Шаг 3. Обработка webhook от Scrive (document.signed)

@app.route("/webhooks/scrive", methods=["POST"])
def scrive_webhook():
    """Обрабатываем уведомление Scrive о подписании документа."""
    event = request.json
    if event.get("event") != "document_signed":
        return {"ok": True}

    doc_id = event.get("document_id")
    if not doc_id:
        return {"ok": True}

    # Находим сделку в Kommo по doc_id (кастомное поле)
    lead_id = find_lead_by_scrive_doc_id(doc_id)
    if not lead_id:
        return {"ok": True}

    # Скачиваем подписанный PDF
    scrive_headers = {"Authorization": f"Bearer {SCRIVE_TOKEN}"}
    pdf_r = requests.get(
        f"{SCRIVE_BASE}/api/v2/documents/{doc_id}/files/sealed",
        headers=scrive_headers,
        timeout=60,
    )
    if pdf_r.ok:
        # Прикрепляем PDF к сделке в Kommo
        attach_pdf_to_lead(lead_id, pdf_r.content, f"contract_signed_{lead_id}.pdf")

    # Меняем этап сделки на "Договор подписан"
    update_lead_stage(lead_id, SIGNED_STAGE_ID)

    return {"ok": True}


def attach_pdf_to_lead(lead_id: int, pdf_bytes: bytes, filename: str):
    """Прикрепляем PDF файл к сделке в Kommo."""
    url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}/files"
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    files = {"file": (filename, pdf_bytes, "application/pdf")}
    requests.post(url, headers=headers, files=files, timeout=30)

Реальный кейс с цифрами

В типовом проекте для B2B-компании из DACH с циклом продаж 3-6 недель и 20-40 договорами в месяц интеграция Kommo + Scrive даёт следующий результат:

До интеграции: менеджер тратил 12-18 минут на цикл «подготовить договор -> отправить -> зафиксировать подписание». При 30 договорах в месяц - 6-9 часов ручной работы.

После интеграции: переход сделки на этап «Договор» автоматически запускает отправку в Scrive. После подписания PDF прикрепляется к сделке, этап меняется автоматически. Ручная работа - фактически нулевая.

Дополнительный эффект - трекинг статусов. Комmo показывает сколько договоров сейчас «на подписании» - отдельный этап воронки. Раньше эта информация была размыта между почтой и Scrive.

Для кого подходит

Интеграция актуальна для компаний, которые:

  • Работают с EU-клиентами и которым важна юридическая сила подписи по eIDAS
  • Используют Scrive из-за поддержки расширенной (AES) и квалифицированной (QES) электронной подписи для контрактов с высокими требованиями
  • Имеют выделенный этап воронки «Договор» в Kommo и хотят автоматизировать переход
  • Работают в Nordics или DACH, где Scrive имеет высокий уровень доверия у юридических служб

Если вы работаете с другими инструментами электронной подписи, логика интеграции аналогична - меняется только API.

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

Нужно ли хранить Scrive Bearer Token в зашифрованном виде? Обязательно. Используйте secrets manager (AWS Secrets Manager, HashiCorp Vault) или как минимум переменные окружения без хранения в коде. Scrive токен даёт полный доступ к вашим документам.

Как настроить webhook от Scrive? В Scrive Admin Panel -> Integrations -> Webhooks. Укажите URL вашего сервиса и выберите событие document_signed. Для верификации Scrive не использует HMAC - проверяйте IP-адрес источника (документированные IP Scrive) или ограничьте endpoint фаерволом.

Что делать, если PDF шаблон меняется при каждой сделке (нужно подставить имя клиента)? Используйте библиотеку reportlab или pypdf для генерации PDF из шаблона с подстановкой данных перед загрузкой в Scrive. Либо используйте механизм Scrive Templates, который позволяет задавать placeholder-поля напрямую в API.

Можно ли добавить несколько подписантов? Да. В массиве parties при создании документа добавьте несколько объектов с role: signing_party. Scrive поддерживает параллельное и последовательное подписание - задаётся через параметр signing_order.

Если вам нужна интеграция Kommo со Scrive - опишите ваш сценарий команде Exceltic.dev. Разберём архитектуру за одну встречу.

Ещё статьи

Все →