Почему нативный подход недостаточен
Encharge - email automation платформа, созданная специально для B2B SaaS. Её главное отличие от Mailchimp или Brevo - поведенческие триггеры на основе продуктовых событий: пользователь не зашёл в продукт 7 дней, завершил onboarding, активировал функцию. В связке с Kommo это открывает интересные возможности: email-сиквенс должен знать, на каком этапе воронки находится лид.
Готовой интеграции Encharge + Kommo нет. Encharge имеет нативные интеграции с HubSpot и Intercom, но не с Kommo. Zapier-вариант существует, но не закрывает ключевую задачу: передачу события из Encharge обратно в Kommo при взаимодействии с письмом.
Для B2B SaaS важна двусторонняя синхронизация: Kommo знает, что лид открыл proposal email - значит пора звонить. Encharge знает, что лид перешёл на этап «Демо» - значит пора запустить последовательность pre-demo emails. Ни Zapier, ни нативные коннекторы этого не дают.
Если вы работаете с кастомными интеграциями для Kommo, то такой двусторонний sync - стандартная задача.
Что реализуется - архитектура решения
Два независимых потока данных:
Поток 1: Kommo -> Encharge
Kommo: смена этапа сделки
--> Webhook --> Python сервис
--> Encharge API: POST /people (upsert контакта)
--> Encharge API: POST /events (track event "deal_moved_to_X")
Поток 2: Encharge -> Kommo
Encharge: email_opened / link_clicked (важный триггер)
--> Webhook --> Python сервис
--> Kommo API: создать Task для SDR
--> Kommo API: добавить Note в сделку
Технические детали
Encharge API Auth. Bearer token. Получается в Encharge Settings -> API & Webhooks -> API Key. Используется как Bearer в заголовке Authorization.
Encharge API эндпоинты:
POST /people- создать или обновить профиль (upsert по email). Тело:{ "email": "...", "fields": { "kommo_stage": "...", "kommo_lead_id": 123 } }POST /events- отправить событие для пользователя. Тело:{ "name": "deal_moved_to_proposal", "email": "user@company.com", "properties": { "lead_id": 123 } }GET /broadcasts/{id}/activity- статистика конкретного broadcast- Webhooks: настраиваются в Encharge UI -> Settings -> Webhooks
Encharge Webhook Events:
email_opened- письмо открытоemail_link_clicked- клик по ссылке в письмеuser_subscribed/user_unsubscribed- изменение статуса подписки
Kommo Webhooks. Событие lead.status_changed при смене этапа сделки.
Пошаговая реализация
Шаг 1. Kommo -> Encharge при смене этапа
import os
import requests
from flask import Flask, request
app = Flask(__name__)
KOMMO_DOMAIN = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
ENCHARGE_TOKEN = os.environ["ENCHARGE_API_KEY"]
ENCHARGE_BASE = "https://api.encharge.io/v1"
# Маппинг ID этапов Kommo на имена событий Encharge
STAGE_TO_EVENT = {
12345: "deal_moved_to_qualified",
12346: "deal_moved_to_demo_scheduled",
12347: "deal_moved_to_proposal_sent",
12348: "deal_moved_to_negotiation",
12349: "deal_moved_to_won",
}
def get_lead_contact_email(lead_id: int) -> tuple[str, str]:
"""Получаем email и имя основного контакта сделки."""
url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}"
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
r = requests.get(url, params={"with": "contacts"}, headers=headers, timeout=10)
if not r.ok:
return "", ""
lead_data = r.json()
contacts = lead_data.get("_embedded", {}).get("contacts", [])
if not contacts:
return "", ""
contact_id = contacts[0]["id"]
cr = requests.get(
f"https://{KOMMO_DOMAIN}/api/v4/contacts/{contact_id}",
headers=headers,
timeout=10,
)
if not cr.ok:
return "", ""
contact = cr.json()
name = contact.get("name", "")
email = ""
for cf in contact.get("custom_fields_values", []) or []:
if cf.get("field_code") == "EMAIL":
email = cf["values"][0]["value"]
break
return email, name
def upsert_encharge_person(email: str, name: str, lead_id: int, stage_name: str):
"""Создаём или обновляем профиль пользователя в Encharge."""
url = f"{ENCHARGE_BASE}/people"
headers = {
"Authorization": f"Bearer {ENCHARGE_TOKEN}",
"Content-Type": "application/json",
}
first, *last_parts = name.split(" ", 1)
payload = {
"email": email,
"firstName": first,
"lastName": last_parts[0] if last_parts else "",
"fields": {
"kommo_lead_id": str(lead_id),
"kommo_current_stage": stage_name,
},
}
r = requests.post(url, json=payload, headers=headers, timeout=10)
return r.ok
def track_encharge_event(email: str, event_name: str, properties: dict):
"""Отправляем событие в Encharge для триггера email flow."""
url = f"{ENCHARGE_BASE}/events"
headers = {
"Authorization": f"Bearer {ENCHARGE_TOKEN}",
"Content-Type": "application/json",
}
payload = {
"name": event_name,
"email": email,
"properties": properties,
}
r = requests.post(url, json=payload, headers=headers, timeout=10)
return r.ok
@app.route("/webhooks/kommo/stage", methods=["POST"])
def kommo_stage_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 not lead_id or new_status_id not in STAGE_TO_EVENT:
return {"ok": True}
event_name = STAGE_TO_EVENT[new_status_id]
email, name = get_lead_contact_email(lead_id)
if not email:
return {"ok": True}
stage_name = event_name.replace("deal_moved_to_", "")
upsert_encharge_person(email, name, lead_id, stage_name)
track_encharge_event(email, event_name, {
"lead_id": lead_id,
"stage": stage_name,
})
return {"ok": True}
Шаг 2. Encharge -> Kommo при взаимодействии с письмом
@app.route("/webhooks/encharge", methods=["POST"])
def encharge_webhook():
"""Обрабатываем события из Encharge."""
event = request.json
event_type = event.get("event")
user_email = event.get("email", "")
# Важные события, при которых нужно действие SDR
high_intent_events = ["email_link_clicked", "email_opened"]
# Для link_clicked - всегда создаём задачу
# Для email_opened - только для определённых broadcast (например, proposal)
broadcast_name = event.get("broadcastName", "")
important_broadcasts = ["proposal", "pricing", "demo_followup"]
should_create_task = (
event_type == "email_link_clicked"
or (
event_type == "email_opened"
and any(b in broadcast_name.lower() for b in important_broadcasts)
)
)
if not should_create_task or not user_email:
return {"ok": True}
# Находим контакт и сделку в Kommo
contact = find_kommo_contact_by_email(user_email)
if not contact:
return {"ok": True}
lead_id = get_active_lead_for_contact(contact["id"])
if not lead_id:
return {"ok": True}
# Создаём задачу для SDR
import time
event_label = "кликнул по ссылке" if event_type == "email_link_clicked" else "открыл письмо"
task_text = (
f"Encharge: {user_email} {event_label} в письме '{broadcast_name}'\n"
f"Рекомендуется связаться в течение 1-2 часов"
)
url = f"https://{KOMMO_DOMAIN}/api/v4/tasks"
headers = {
"Authorization": f"Bearer {KOMMO_TOKEN}",
"Content-Type": "application/json",
}
payload = [{
"task_type_id": 1,
"text": task_text,
"complete_till": int(time.time()) + 2 * 3600,
"entity_id": lead_id,
"entity_type": "leads",
}]
requests.post(url, json=payload, headers=headers, timeout=10)
# Добавляем Note в сделку для истории
note_url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}/notes"
note_payload = [{"note_type": "common", "params": {
"text": f"Encharge signal: {user_email} {event_label} в '{broadcast_name}'"
}}]
requests.post(note_url, json=note_payload, headers=headers, timeout=10)
return {"ok": True}
def find_kommo_contact_by_email(email: str) -> dict | None:
url = f"https://{KOMMO_DOMAIN}/api/v4/contacts"
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
r = requests.get(url, params={"query": email}, headers=headers, timeout=10)
if r.ok:
contacts = r.json().get("_embedded", {}).get("contacts", [])
return contacts[0] if contacts else None
return None
def get_active_lead_for_contact(contact_id: int) -> int | None:
url = f"https://{KOMMO_DOMAIN}/api/v4/contacts/{contact_id}/links"
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
r = requests.get(url, headers=headers, timeout=10)
if not r.ok:
return None
links = r.json().get("_embedded", {}).get("links", [])
lead_ids = [l["to_entity_id"] for l in links if l.get("to_entity_type") == "leads"]
return lead_ids[0] if lead_ids else None
Реальный кейс с цифрами
Для B2B SaaS-компании с MRR от $30k двусторонняя синхронизация Kommo + Encharge создаёт важный эффект: sales-команда и маркетинг работают с единым контекстом.
Типичный сценарий до интеграции: маркетолог запускает nurture-сиквенс «для всех лидов на этапе Qualified» - вручную выгружая список из Kommo в Excel раз в неделю. SDR не знает, что лид получает письма. Лид получает письмо сразу после звонка с SDR - и это выглядит странно.
После интеграции: при переходе сделки на этап «Qualified» в Encharge автоматически запускается сиквенс из 5 писем на 10 дней. При переходе на «Proposal Sent» - сиквенс останавливается, запускается «Proposal Follow-up» на 3 письма. Если лид кликает по ссылке в proposal email - SDR получает задачу в Kommo через 2-5 минут. По данным схожих интеграций, конверсия из «Proposal Sent» в «Won» вырастает на 15-25% за счёт своевременного follow-up.
Для кого подходит
Интеграция актуальна для B2B SaaS-компаний, которые:
- Используют Encharge для email-автоматизации с поведенческими триггерами
- Ведут воронку в Kommo с несколькими чёткими этапами
- Хотят синхронизировать маркетинговые email-sequences с актуальным этапом продаж
- Имеют SDR, для которых важен сигнал об активности лида в письмах
Если вы используете другие email-инструменты - например, для синхронизации с Kommo + ActiveCampaign - логика схожа, но Encharge лучше подходит именно для SaaS с продуктовыми событиями.
Часто задаваемые вопросы
Как не получать задачу в Kommo при каждом открытии письма? Добавьте дедупликацию: сохраняйте последнее время создания задачи для каждой сделки в Redis или базе данных. Если прошло менее 24 часов с последней задачи по этой сделке - не создавайте новую.
Encharge отправляет webhook при каждом открытии или только при первом?
По умолчанию - при каждом. Для фильтрации используйте поле isFirstEvent: true в payload, если оно есть, или реализуйте собственную дедупликацию как описано выше.
Можно ли синхронизировать кастомные свойства из Kommo в Encharge?
Да. В Encharge fields принимает произвольные ключи. Добавьте в запрос upsert_encharge_person любые кастомные поля из Kommo: размер компании, индустрия, должность контакта из кастомных полей сделки.
Что происходит, если лид unsubscribe в Encharge?
Encharge отправит webhook user_unsubscribed. Рекомендуется при этом добавлять тег «Email Unsubscribed» в Kommo-контакт, чтобы SDR видели, что email-маркетинг для этого лида недоступен, и могли сосредоточиться на звонках.
Если вам нужна интеграция Kommo с Encharge - опишите ваш стек и сценарий команде Exceltic.dev. Разберём архитектуру за одну встречу.