Kommo + Zoho Sign: электронная подпись документов из воронки продаж
Zoho Sign - EU-friendly платформа электронной подписи с серверами в Нидерландах и соответствием eIDAS SES/AES. Нативной интеграции с Kommo нет. Правильный путь - Zoho Sign REST API v1 с OAuth2-аутентификацией через Zoho Accounts.
Сценарий интеграции
- Сделка переходит в стадию «Contract» в Kommo
- Webhook Kommo инициирует создание signing request из шаблона
- Zoho Sign отправляет документ контакту на подпись
- Webhook
document.completed-> подписанный PDF-URL -> Note в Kommo - Сделка автоматически переходит в следующую стадию
Zoho OAuth2: особенности для EU
Zoho использует региональные домены для OAuth. Для EU-аккаунтов (Netherlands DC):
Authorization: https://accounts.zoho.eu/oauth/v2/auth
Token: https://accounts.zoho.eu/oauth/v2/token
API base: https://sign.zoho.eu/api/v1
Для US-аккаунтов: zoho.com. Ошибка в домене приводит к invalid_client или access_denied.
Регистрация приложения:
- Zoho Developer Console -> Create New Client -> Server-based Applications
- Указать redirect URI (например,
https://your-service.com/zoho/callback) - Запросить scope:
ZohoSign.requests.CREATE,ZohoSign.requests.READ,ZohoSign.documents.READ
Получение токена (Authorization Code Flow):
import requests
ZOHO_CLIENT_ID = "your_client_id"
ZOHO_CLIENT_SECRET = "your_client_secret"
ZOHO_REFRESH_TOKEN = "your_refresh_token" # сохранить после первичной авторизации
ZOHO_DC = "eu" # "com" для US
TOKEN_URL = f"https://accounts.zoho.{ZOHO_DC}/oauth/v2/token"
SIGN_BASE = f"https://sign.zoho.{ZOHO_DC}/api/v1"
def get_access_token() -> str:
r = requests.post(TOKEN_URL, data={
"refresh_token": ZOHO_REFRESH_TOKEN,
"client_id": ZOHO_CLIENT_ID,
"client_secret": ZOHO_CLIENT_SECRET,
"grant_type": "refresh_token",
})
r.raise_for_status()
return r.json()["access_token"]
Access token живёт 1 час. Refresh token не истекает если не отозван вручную.
Создание signing request из шаблона
Zoho Sign поддерживает шаблоны с полями (подпись, дата, имя). При создании request подставляете данные клиента.
Список шаблонов:
def list_templates(token: str) -> list:
r = requests.get(
f"{SIGN_BASE}/templates",
headers={"Authorization": f"Zoho-oauthtoken {token}"},
)
r.raise_for_status()
return r.json().get("templates", [])
Заголовок Zoho-oauthtoken (не Bearer) - стандарт для всего Zoho API.
Создание signing request:
def create_signing_request(token: str, template_id: str, contact: dict, deal: dict) -> str:
"""Send document for signing. Returns document request ID."""
payload = {
"templates": {
"template_id": template_id,
"actions": [
{
"action_type": "SIGN",
"recipient_name": f"{contact['first_name']} {contact['last_name']}",
"recipient_email": contact["email"],
"signing_order": 1,
"private_notes": f"Kommo Deal #{deal['id']}",
}
],
"notes": f"Deal: {deal['name']}",
},
"data": {
"field_data": {
"field_text_data": {
"CompanyName": contact.get("company", ""),
"ContractDate": "2026-05-27",
"DealAmount": str(deal.get("price", "")),
}
}
},
}
r = requests.post(
f"{SIGN_BASE}/templates/{template_id}/createdocument",
headers={
"Authorization": f"Zoho-oauthtoken {token}",
"Content-Type": "application/json",
},
json={"data": json.dumps(payload)}, # Zoho Sign требует JSON как строку в поле "data"
)
r.raise_for_status()
return r.json()["requests"]["request_id"]
Важная деталь: тело запроса отправляется как multipart/form-data с полем data, содержащим JSON-строку. Стандартный json=... не работает - нужен data={"data": json.dumps(payload)}.
Webhook от Zoho Sign
Настройка в Zoho Sign Settings > Webhooks:
- Event:
document.completed,document.declined - URL: ваш эндпоинт
- Secret: для HMAC верификации
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
ZOHO_WEBHOOK_SECRET = "your_webhook_secret"
@app.route("/zoho-sign/webhook", methods=["POST"])
def zoho_sign_webhook():
signature = request.headers.get("X-Zoho-Webhook-Token", "")
body = request.get_data()
expected = hmac.new(
ZOHO_WEBHOOK_SECRET.encode(),
body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(signature, expected):
abort(401)
data = request.json
event_type = data.get("notifications", {}).get("operation", "")
request_id = data.get("requests", {}).get("request_id", "")
signed_url = data.get("requests", {}).get("sign_url", "")
if event_type == "RequestCompleted":
# Извлечь kommo deal ID из заметки или сохранённого маппинга
deal_id = get_deal_id_by_request(request_id)
add_kommo_note(deal_id, f"Zoho Sign: документ подписан. PDF: {signed_url}")
advance_kommo_deal_stage(deal_id)
return "ok", 200
Скачивание подписанного PDF
После RequestCompleted можно скачать PDF:
def download_signed_pdf(token: str, request_id: str) -> bytes:
r = requests.get(
f"{SIGN_BASE}/requests/{request_id}/pdf",
headers={"Authorization": f"Zoho-oauthtoken {token}"},
)
r.raise_for_status()
return r.content # PDF bytes
PDF загружается в Kommo как вложение через /api/v4/leads/{id}/notes с base64-encoded содержимым.
Реальный кейс
EU IT-дистрибьютор с 40-50 контрактами в месяц отправлял документы вручную через Zoho Sign UI и вручную отмечал подписание в Kommo. Задержка между подписанием и обновлением CRM составляла 1-3 часа.
После интеграции:
- Документ уходит клиенту автоматически при переходе сделки в «Contract»
- Подписание отражается в Kommo в течение 10 секунд через webhook
- PDF-ссылка добавляется Note без участия менеджера
В типовом месяце - экономия ~4 часов рутинной работы на 45 контрактах.
Для кого подходит
Компании в EU, которые используют или рассматривают Zoho Sign как часть Zoho-экосистемы. Если команда уже на Zoho CRM/Books/Sign - аутентификация через одно OAuth2-приложение покрывает все продукты.
Для DocuSign - см. Kommo + DocuSign. Для open-source решения - Kommo + Docuseal.
Часто задаваемые вопросы
Какой уровень eIDAS поддерживает Zoho Sign?
Zoho Sign поддерживает Simple Electronic Signature (SES) по умолчанию для большинства документов. Advanced Electronic Signature (AES) доступен через интеграцию с Aadhaar или European trust services. Для EU B2B-контрактов SES достаточен для большинства юрисдикций - AES требуется для недвижимости, HR-документов в отдельных странах.
Как работает маппинг полей шаблона?
Поля в шаблоне задаются через Zoho Sign Template Editor. Каждое текстовое поле имеет имя (CompanyName, ContractDate и т.д.). В API вы передаёте field_text_data с этими именами как ключами. Подписи и инициалы не передаются через API - они заполняются получателем в UI Zoho Sign.
Что если клиент не подписывает в течение N дней?
Настройте expiry_days при создании request (максимум 90 дней). При истечении Zoho Sign отправляет webhook document.expired. Обработчик может создать задачу в Kommo для менеджера и отправить новый request при необходимости.
Можно ли отправить документ нескольким подписантам?
Да. В actions укажите несколько объектов с signing_order 1, 2, 3. Zoho Sign отправит документ последовательно или параллельно в зависимости от настройки. Webhook document.completed срабатывает только когда все подписи получены.
Итог
Ключевые точки интеграции Kommo + Zoho Sign:
- Zoho OAuth2: региональный домен (
zoho.euдля EU), заголовокZoho-oauthtoken - Request из шаблона: тело передаётся как JSON-строка в поле
datamultipart - Webhook: HMAC-SHA256 через
X-Zoho-Webhook-Token - Скачивание PDF через
/api/v1/requests/{id}/pdf
Если ваша команда работает с Zoho-продуктами и нужна интеграция с Kommo - опишите стек команде Exceltic.dev. Разберём, какие данные и между какими системами нужно синхронизировать.