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

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

Zoho Sign - EU-friendly платформа электронной подписи с серверами в Нидерландах и соответствием eIDAS SES/AES. Нативной интеграции с Kommo нет. Правильный путь - Zoho Sign REST API v1 с OAuth2-аутентификацией через Zoho Accounts.

Сценарий интеграции

  1. Сделка переходит в стадию «Contract» в Kommo
  2. Webhook Kommo инициирует создание signing request из шаблона
  3. Zoho Sign отправляет документ контакту на подпись
  4. Webhook document.completed -> подписанный PDF-URL -> Note в Kommo
  5. Сделка автоматически переходит в следующую стадию

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.

Регистрация приложения:

  1. Zoho Developer Console -> Create New Client -> Server-based Applications
  2. Указать redirect URI (например, https://your-service.com/zoho/callback)
  3. Запросить 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-строка в поле data multipart
  • Webhook: HMAC-SHA256 через X-Zoho-Webhook-Token
  • Скачивание PDF через /api/v1/requests/{id}/pdf

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

Ещё статьи

Все →