Kommo + Docuseal: автоматическое подписание документов через open-source платформу

Kommo + Docuseal: автоматическое подписание документов через open-source платформу

Docuseal — open-source платформа электронной подписи: self-hosted или cloud, без вендор-локина и с REST API. Для команд, которые хотят e-sign без $25+/пользователь/месяц за DocuSign или Adobe Sign, Docuseal — функциональная альтернатива с полным API-доступом. Без интеграции с Kommo менеджер создаёт submission вручную после Won. С интеграцией Won автоматически отправляет контракт из шаблона с заполненными данными сделки — клиент получает ссылку для подписания в минуту.

Open-source vs SaaS для e-sign

Docuseal self-hosted (Docker) даёт:
— Полный контроль данных — все документы на вашем сервере
— Неограниченное количество документов без тарифных лимитов
— EU GDPR compliance без передачи данных американским SaaS
— Единоразовые затраты на хостинг вместо $25–40/пользователь/месяц

Docuseal cloud (docuseal.co) — тот же API, без управления инфраструктурой. Для небольших команд удобнее.

Оба варианта используют одинаковый REST API. Для кастомной интеграции с Kommo это означает: один код, легко переключить между self-hosted и cloud.

Что синхронизируется

Kommo -> Docuseal:
— Won -> создать submission из шаблона с полями из сделки (имя, email, сумма, тариф)
— Won -> установить порядок подписантов (клиент -> внутренняя подпись)
— Автоматически отправить email с ссылкой на подпись

Docuseal -> Kommo:
submission.completed -> Note: «Docuseal: документ подписан всеми сторонами» + смена этапа
submission.declined -> Note + задача: «Клиент отклонил подписание»
submission.expired -> Note + задача: «Срок подписания истёк»
form.completed (один подписант завершил) -> Note: «{имя} подписал»

Архитектура

Kommo Webhook: сделка перешла в Won
  ↓ Backend
  1. GET /api/v4/leads/{id} + contacts
     -> email, имя, тариф, сумма
  2. Docuseal API: POST /api/submissions
     -> template_id, submitters с полями из сделки
  3. Kommo: PATCH /leads/{id}
     -> docuseal_submission_id = submission.id
  4. Kommo: POST /leads/{id}/notes
     -> «Docuseal: контракт отправлен на подпись {email}»

Docuseal Webhook: submission.completed
  ↓ Backend
  1. Верификация X-Docuseal-Signature
  2. Найти сделку по docuseal_submission_id
  3. Kommo: PATCH /leads/{id} -> смена этапа «Контракт подписан»
  4. Kommo: POST /notes -> «Docuseal: все подписи собраны»

Docuseal API: ключевые запросы

Base URL (cloud): https://api.docuseal.co.
Base URL (self-hosted): https://your-docuseal-domain.com.
Аутентификация: X-Auth-Token: {api_key} (взять в настройках аккаунта).

Создать submission из шаблона:

import requests

DOCUSEAL_API_KEY = "your_api_key"
DOCUSEAL_BASE_URL = "https://api.docuseal.co"  # или self-hosted URL

headers = {
    "X-Auth-Token": DOCUSEAL_API_KEY,
    "Content-Type": "application/json"
}

def create_submission(template_id: int, submitters: list,
                      send_email: bool = True) -> dict:
    resp = requests.post(
        f"{DOCUSEAL_BASE_URL}/api/submissions",
        headers=headers,
        json={
            "template_id": template_id,
            "send_email": send_email,
            "submitters": submitters
        }
    )
    resp.raise_for_status()
    return resp.json()

def on_deal_won(lead: dict, contact: dict):
    email = get_contact_email(contact)
    name = contact["name"]
    plan = get_custom_field(lead, PLAN_FIELD_ID) or "Growth"
    amount = lead.get("price", 0)

    # submitters - порядок: сначала клиент, потом внутренняя подпись
    submitters = [
        {
            "email": email,
            "name": name,
            "role": "Client",
            "values": {
                "client_name": name,
                "client_email": email,
                "plan": plan,
                "contract_amount": str(amount),
                "contract_date": datetime.now().strftime("%d.%m.%Y"),
            }
        },
        {
            "email": INTERNAL_SIGNER_EMAIL,
            "name": INTERNAL_SIGNER_NAME,
            "role": "Company",
        }
    ]

    submission = create_submission(DOCUSEAL_TEMPLATE_ID, submitters)
    submission_id = submission[0]["submission_id"]  # возвращает список submitters

    update_kommo_deal(lead["id"], {"docuseal_submission_id": str(submission_id)})
    create_kommo_note(lead["id"],
        f"Docuseal: контракт #{submission_id} отправлен на подпись -> {email}")

Получить статус submission:

def get_submission_status(submission_id: int) -> dict:
    resp = requests.get(
        f"{DOCUSEAL_BASE_URL}/api/submissions/{submission_id}",
        headers=headers
    )
    resp.raise_for_status()
    return resp.json()

Обработка Docuseal Webhook:

import hmac, hashlib
from flask import Flask, request, abort

app = Flask(__name__)
DOCUSEAL_WEBHOOK_SECRET = "your_webhook_secret"

def verify_signature(payload: bytes, sig_header: str) -> bool:
    expected = hmac.new(
        DOCUSEAL_WEBHOOK_SECRET.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, sig_header)

@app.route("/webhooks/docuseal", methods=["POST"])
def docuseal_webhook():
    sig = request.headers.get("X-Docuseal-Signature", "")
    if not verify_signature(request.data, sig):
        abort(403)

    payload = request.json
    event = payload.get("event_type")
    submission_id = str(payload.get("data", {}).get("submission_id", ""))

    deal_id = find_deal_by_field("docuseal_submission_id", submission_id)
    if not deal_id:
        return "", 200

    if event == "submission.completed":
        update_kommo_deal(deal_id, {"stage_id": STAGE_CONTRACT_SIGNED})
        create_kommo_note(deal_id,
            "Docuseal: все подписи собраны - документ оформлен")

    elif event == "submission.declined":
        create_kommo_note(deal_id, "Docuseal: клиент отклонил подписание")
        create_kommo_task(deal_id, "Обсудить условия - Docuseal зафиксировал отказ")

    elif event == "submission.expired":
        create_kommo_note(deal_id, "Docuseal: срок подписания истёк")
        create_kommo_task(deal_id, "Отправить новую ссылку на подписание")

    elif event == "form.completed":
        submitter_name = payload.get("data", {}).get("submitter", {}).get("name", "")
        create_kommo_note(deal_id, f"Docuseal: {submitter_name} подписал")

    return "", 200

Webhook настройка в Docuseal: Settings -> Webhooks -> Add Webhook. Указать URL и secret. События: submission.completed, submission.declined, submission.expired, form.completed.

Self-hosted: установка за 5 минут

# docker-compose.yml
version: '3'
services:
  docuseal:
    image: docuseal/docuseal:latest
    ports:
      - "3000:3000"
    volumes:
      - ./data:/data
    environment:
      - SECRET_KEY_BASE=your_secret_key
      - DATABASE_URL=sqlite3:///data/docuseal.sqlite3

docker compose up -d -> Docuseal доступен на порту 3000. Для production: Nginx reverse proxy + SSL + PostgreSQL вместо SQLite.

Реальный кейс

Юридическая компания (EU, 50–70 контрактов в месяц, Kommo + Docuseal self-hosted):

  • До: DocuSign $40/пользователь/месяц × 8 человек = $320/мес. Шаблоны настраивались вручную, данные из Kommo копировались. Задержка отправки 30–90 минут.
  • После: Docuseal self-hosted на VPS €20/мес. Won -> контракт с заполненными данными за 8 секунд. Сохранение €3,620/год.
  • Дополнительно: EU GDPR — все документы на собственном сервере, не передаются американским SaaS. Аргумент при работе с EU-клиентами.

Для кого актуально

  • Команды с GDPR-требованиями к хранению документов (юридические, медицинские, финансовые)
  • Те, кто хочет контролировать затраты — self-hosted убирает per-user-per-month плату
  • Разработчики, которым нужен открытый API без закрытых ограничений
  • 20+ контрактов в месяц — при меньшем объёме ручная работа ещё оправдана

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

Docuseal vs DocuSign: реально ли для enterprise?

Docuseal покрывает 80% корпоративных use cases: шаблоны, порядок подписантов, аудит-лог, скачивание подписанного PDF. Не хватает: расширенного workflow (conditional routing), Salesforce-нативной интеграции, enterprise SSO (в roadmap). Для команд до 50 человек — достаточно.

Какие форматы шаблонов поддерживает Docuseal?

PDF с полями для заполнения. Можно создавать шаблоны прямо в Docuseal UI (drag-and-drop поля на PDF). API принимает template_id — ID шаблона из Docuseal. Один шаблон можно использовать многократно с разными данными через API.

Docuseal юридически значим в EU?

Да. Подписи через Docuseal соответствуют eIDAS (EU Electronic Signatures Regulation) как Simple Electronic Signature (SES). Для квалифицированной подписи (QES) нужен отдельный провайдер идентификации — Docuseal её не генерирует.

Как загружать шаблоны через API?

POST /api/templates с multipart/form-data — загрузить PDF-файл. Ответ содержит id шаблона для дальнейшего использования в submissions. Шаблоны можно создавать и через UI — API использует тот же ID.

Итого

  • Docuseal: X-Auth-Token в заголовке, https://api.docuseal.co (cloud) или self-hosted URL
  • Создать submission: POST /api/submissions с template_id и массивом submitters
  • submitters[].values — автозаполнение полей шаблона данными из Kommo
  • Webhook: HMAC-SHA256 через X-Docuseal-Signature
  • Self-hosted: Docker, данные на вашем сервере, EU GDPR без вопросов
  • Ключевые события: submission.completed, submission.declined, form.completed

Если вы хотите автоматизировать подписание контрактов из Kommo через Docuseal — опишите структуру шаблонов и порядок подписантов. Exceltic.dev настроит интеграцию.

Ещё статьи

Все →