Signeasy - платформа электронной подписи с поддержкой eIDAS (EU), ESIGN Act (US), и IT Act (Индия). Позиционируется как более простой и доступный альтернатив DocuSign для малого и среднего бизнеса. Поддерживает шаблоны, поля для заполнения, последовательную и параллельную подпись. Кастомная интеграция с Kommo решает главную проблему нативных eSign-коннекторов: конверт создаётся и подписывается, но статус не попадает обратно в карточку сделки.
Signeasy REST API работает с Bearer-токеном. Ключевые операции: создать запрос на подпись из шаблона, отслеживать статус, получить уведомление о подписании через webhook.
Signeasy Template - предварительно заготовленный документ с полями для заполнения (имя, дата, сумма). При создании запроса на подпись поля заполняются данными из CRM-сделки.
Что не работает без интеграции
Стандартный процесс: менеджер скачивает шаблон -> вручную вносит данные клиента -> загружает в Signeasy -> отправляет на подпись -> следит за статусом вне Kommo.
Два разрыва: данные из сделки не попадают в документ автоматически, статус подписания не возвращается в Kommo.
Архитектура
Kommo: сделка -> этап "Отправить контракт"
-> Kommo webhook -> Ваш сервер
Ваш сервер
-> Получить данные сделки (имя, email, сумма)
-> Signeasy API: POST /v1/signature_requests
{template_id, signer.email, signer.name, fields: {amount, company}}
-> Kommo: записать signeasy_request_id в custom field
Клиент подписывает -> Signeasy
-> Signeasy webhook: signature_request.signed
-> Ваш сервер
-> Kommo: перевести сделку -> следующий этап
-> Kommo: добавить ссылку на подписанный PDF
Реализация: отправка на подпись
import requests, os, hmac, hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
SE_TOKEN = os.environ["SIGNEASY_API_TOKEN"]
SE_TEMPLATE_ID = os.environ["SIGNEASY_TEMPLATE_ID"]
SE_WEBHOOK_SEC = os.environ["SIGNEASY_WEBHOOK_SECRET"]
KOMMO_SUBDOMAIN = os.environ["KOMMO_SUBDOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
SIGN_STAGE_ID = int(os.environ["KOMMO_SIGN_STAGE_ID"])
SIGNED_STAGE_ID = int(os.environ["KOMMO_SIGNED_STAGE_ID"])
CF_REQUEST_ID = int(os.environ["KOMMO_CF_SIGNEASY_REQUEST_ID"])
SE_BASE = "https://api.signeasy.com"
SE_HDR = {"Authorization": f"Bearer {SE_TOKEN}", "Content-Type": "application/json"}
KOMMO_BASE = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4"
KOMMO_HDR = {"Authorization": f"Bearer {KOMMO_TOKEN}", "Content-Type": "application/json"}
def get_lead_with_contact(lead_id: int) -> tuple[dict, dict]:
r = requests.get(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
params={"with": "contacts,custom_fields_values"},
)
lead = r.json()
contacts = lead.get("_embedded", {}).get("contacts", [])
contact = {}
if contacts:
rc = requests.get(
f"{KOMMO_BASE}/contacts/{contacts[0]['id']}",
headers=KOMMO_HDR,
params={"with": "custom_fields_values"},
)
contact = rc.json()
return lead, contact
def extract_email(contact: dict) -> str:
for cf in contact.get("custom_fields_values", []) or []:
if cf.get("field_code") == "EMAIL":
vals = cf.get("values", [])
if vals:
return vals[0].get("value", "")
return ""
def create_signature_request(signer_name: str, signer_email: str, fields: dict) -> str:
payload = {
"template_id": SE_TEMPLATE_ID,
"message": "Пожалуйста, подпишите договор.",
"signers": [{
"name": signer_name,
"email": signer_email,
"role": "Signer",
}],
"prefill_fields": [
{"api_key": k, "value": str(v)}
for k, v in fields.items()
],
}
r = requests.post(f"{SE_BASE}/v1/signature_requests", headers=SE_HDR, json=payload)
r.raise_for_status()
return r.json()["id"]
def save_request_id(lead_id: int, request_id: str):
requests.patch(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
json={"custom_fields_values": [{
"field_id": CF_REQUEST_ID,
"values": [{"value": request_id}],
}]},
)
@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
data = request.json or {}
for lead_data in data.get("leads", {}).get("status", []):
lead_id = lead_data.get("id")
new_status = lead_data.get("status_id")
if new_status != SIGN_STAGE_ID:
continue
lead, contact = get_lead_with_contact(lead_id)
signer_email = extract_email(contact)
signer_name = contact.get("name", "")
if not signer_email:
continue
fields = {
"company_name": lead.get("name", ""),
"contract_amount": str(lead.get("price", 0)),
"kommo_lead_id": str(lead_id),
}
req_id = create_signature_request(signer_name, signer_email, fields)
save_request_id(lead_id, req_id)
return jsonify({"status": "ok"}), 200
Реализация: webhook при подписании
def verify_signeasy_signature(body: bytes, sig_header: str) -> bool:
# Signeasy HMAC-SHA256 hex digest
computed = hmac.new(SE_WEBHOOK_SEC.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(computed, sig_header)
@app.route("/webhooks/signeasy", methods=["POST"])
def signeasy_webhook():
sig = request.headers.get("X-Signeasy-Signature", "")
if not verify_signeasy_signature(request.data, sig):
return jsonify({"error": "invalid signature"}), 401
event = request.json or {}
event_type = event.get("event_type", "")
if event_type not in ("signature_request.signed", "signature_request.declined"):
return jsonify({"status": "ignored"}), 200
# Найти kommo_lead_id в prefill_fields
doc = event.get("document", {})
prefills = doc.get("prefill_fields", [])
lead_id = None
for f in prefills:
if f.get("api_key") == "kommo_lead_id":
lead_id = f.get("value")
break
if not lead_id:
return jsonify({"status": "no_lead_id"}), 200
if event_type == "signature_request.signed":
signed_url = doc.get("signed_document_url", "")
# Перевести сделку в следующий этап
requests.patch(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
json={"status_id": SIGNED_STAGE_ID},
)
# Добавить ссылку на подписанный PDF
requests.post(
f"{KOMMO_BASE}/notes",
headers=KOMMO_HDR,
json=[{
"entity_id": int(lead_id),
"entity_type": "leads",
"note_type": "common",
"params": {"text": f"Signeasy: договор подписан. PDF: {signed_url}"},
}],
)
elif event_type == "signature_request.declined":
decliner = event.get("signer", {}).get("name", "клиент")
requests.post(
f"{KOMMO_BASE}/notes",
headers=KOMMO_HDR,
json=[{
"entity_id": int(lead_id),
"entity_type": "leads",
"note_type": "common",
"params": {"text": f"Signeasy: договор отклонён подписантом {decliner}. Уточните причину."},
}],
)
return jsonify({"status": "ok"}), 200
Настройка Signeasy
- Signeasy -> Settings -> API Access -> Generate API Token
- Templates -> создать шаблон контракта с полями
company_name,contract_amount,kommo_lead_id- Поле
kommo_lead_idскрытое (Hidden field) - заполняется API, не показывается подписанту
- Поле
- Webhooks -> Add webhook endpoint
- URL:
https://your-server.com/webhooks/signeasy - Events: signature_request.signed, signature_request.declined, signature_request.expired
- URL:
- Скопировать Webhook Secret для HMAC-верификации
Поддержка eIDAS (EU)
Signeasy поддерживает Simple Electronic Signature (SES) - соответствует требованиям eIDAS для большинства B2B-контрактов в ЕС. Для контрактов, требующих Advanced Electronic Signature (AES) или Qualified (QES) - нужны провайдеры с EU Trust List (Yousign, Scrive).
Реальный кейс
SaaS-компания, 8 AE, 30 контрактов в месяц. До интеграции: менеджеры тратили 20 минут на каждый контракт (данные из Kommo -> Word -> PDF -> Signeasy). После: запрос создаётся автоматически при переводе сделки в “Отправить контракт”. Экономия: 10 часов в месяц на команду.
Для кого актуально
B2B SaaS и сервисные компании с циклом продаж 15-60 дней, контрактами на $1000+ и командой продаж 3-20 человек. Особенно если документооборот - узкое место (задержки с подписанием тормозят revenue recognition).
Похожий подход описан для Kommo + DocuSign и Kommo + Dropbox Sign.
Часто задаваемые вопросы
Чем Signeasy отличается от DocuSign и Adobe Sign?
Signeasy дешевле ($8-24/user/mo vs $15-45 у DocuSign) и проще в настройке API. Не имеет нативной интеграции с HubSpot или Salesforce из коробки - только через API или Zapier. Для небольших команд (до 20 человек) и простых контрактов - полноценная альтернатива.
Как добавить несколько подписантов (sequential signing)?
В signers передать массив: [{name, email, role, signing_order: 1}, {name, email, role, signing_order: 2}]. Второй подписант получит запрос только после того как первый подпишет. Webhook signature_request.signed приходит когда все подписанты завершили.
Можно ли скачать подписанный PDF через API?
Да: GET /v1/signature_requests/{id}/download - вернёт signed PDF. Можно загрузить его в Kommo как attachment через Kommo Files API. Это полезно для архивирования - подписанный документ хранится прямо в карточке сделки.
Итог
Kommo + Signeasy - автоматизация документооборота в воронке продаж:
- Kommo webhook
leads.status.changed->POST /v1/signature_requestsсprefill_fields - Скрытое поле
kommo_lead_idдля обратной корреляции - HMAC-SHA256 верификация webhook (
X-Signeasy-Signature) signature_request.signed-> перевести Deal + добавить ссылку на PDFsignature_request.declined-> создать задачу менеджеру
Если нужна интеграция Kommo с Signeasy или другим eSign-инструментом - обратитесь в Exceltic.dev.