Why the native approach falls short
Encharge is an email automation platform built specifically for B2B SaaS. Its key differentiator from Mailchimp or Brevo is behavioral triggers based on product events: a user hasn’t logged into the product in 7 days, completed onboarding, activated a feature. Combined with Kommo, this opens up interesting possibilities: an email sequence needs to know what pipeline stage the lead is at.
There is no ready-made Encharge + Kommo integration. Encharge has native integrations with HubSpot and Intercom but not with Kommo. A Zapier approach exists but doesn’t cover the core requirement: passing an event from Encharge back to Kommo when a lead interacts with an email.
For B2B SaaS, bidirectional sync matters: Kommo knows the lead opened the proposal email - time to call. Encharge knows the lead moved to the “Demo” stage - time to launch a pre-demo email sequence. Neither Zapier nor native connectors deliver this.
If you work with custom integrations for Kommo, this kind of bidirectional sync is a standard task.
What gets built - solution architecture
Two independent data flows:
Flow 1: Kommo -> Encharge
Kommo: deal stage change
--> Webhook --> Python service
--> Encharge API: POST /people (upsert contact)
--> Encharge API: POST /events (track event "deal_moved_to_X")
Flow 2: Encharge -> Kommo
Encharge: email_opened / link_clicked (key trigger)
--> Webhook --> Python service
--> Kommo API: create Task for SDR
--> Kommo API: add Note to deal
Technical details
Encharge API Auth. Bearer token. Obtained in Encharge Settings -> API & Webhooks -> API Key. Used as Bearer in the Authorization header.
Encharge API endpoints:
POST /people- create or update a profile (upsert by email). Body:{ "email": "...", "fields": { "kommo_stage": "...", "kommo_lead_id": 123 } }POST /events- send an event for a user. Body:{ "name": "deal_moved_to_proposal", "email": "user@company.com", "properties": { "lead_id": 123 } }GET /broadcasts/{id}/activity- statistics for a specific broadcast- Webhooks: configured in Encharge UI -> Settings -> Webhooks
Encharge Webhook Events:
email_opened- email was openedemail_link_clicked- click on a link in the emailuser_subscribed/user_unsubscribed- subscription status change
Kommo Webhooks. The lead.status_changed event on deal stage change.
Step-by-step implementation
Step 1. Kommo -> Encharge on stage change
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"
# Mapping of Kommo stage IDs to Encharge event names
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]:
"""Get email and name of the primary deal contact."""
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):
"""Create or update a user profile in 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):
"""Send an event to Encharge to trigger an 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():
"""Handle deal stage change in 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}
Step 2. Encharge -> Kommo on email interaction
@app.route("/webhooks/encharge", methods=["POST"])
def encharge_webhook():
"""Handle events from Encharge."""
event = request.json
event_type = event.get("event")
user_email = event.get("email", "")
# High-intent events requiring SDR action
high_intent_events = ["email_link_clicked", "email_opened"]
# For link_clicked - always create a task
# For email_opened - only for certain broadcasts (e.g. 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}
# Find contact and deal in 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}
# Create task for SDR
import time
event_label = "clicked a link" if event_type == "email_link_clicked" else "opened the email"
task_text = (
f"Encharge: {user_email} {event_label} in '{broadcast_name}'\n"
f"Recommended: follow up within 1-2 hours"
)
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)
# Add Note to deal for history
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} in '{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
Real case with numbers
For a B2B SaaS company with MRR above $30k, bidirectional Kommo + Encharge sync creates an important effect: the sales team and marketing work with a shared context.
A typical scenario before the integration: a marketer launches a nurture sequence “for all leads at the Qualified stage” - by manually exporting a list from Kommo to Excel once a week. The SDR doesn’t know the lead is receiving emails. The lead gets an email right after a call with an SDR - which feels awkward.
After the integration: when a deal moves to “Qualified,” Encharge automatically starts a 5-email, 10-day sequence. When it moves to “Proposal Sent” - that sequence stops and a 3-email “Proposal Follow-up” launches. If the lead clicks a link in the proposal email - the SDR gets a task in Kommo within 2-5 minutes. Based on comparable integrations, conversion from “Proposal Sent” to “Won” increases 15-25% thanks to timely follow-up.
Who this is for
The integration is relevant for B2B SaaS companies that:
- Use Encharge for email automation with behavioral triggers
- Run their pipeline in Kommo with several distinct stages
- Want to sync marketing email sequences with the current sales stage
- Have SDRs who need signals about lead activity in emails
If you use other email tools - for example, for syncing with Kommo + ActiveCampaign - the logic is similar, but Encharge is better suited specifically for SaaS with product events.
Frequently asked questions
How do I avoid creating a task in Kommo on every email open? Add deduplication: store the last task creation time per deal in Redis or a database. If less than 24 hours have passed since the last task for this deal - skip creating a new one.
Does Encharge send a webhook on every open or only the first?
By default - on every open. To filter, use the isFirstEvent: true field in the payload if available, or implement your own deduplication as described above.
Can I sync custom properties from Kommo to Encharge?
Yes. Encharge fields accepts arbitrary keys. Add any custom fields from Kommo to the upsert_encharge_person request: company size, industry, contact’s job title from deal custom fields.
What happens if a lead unsubscribes in Encharge?
Encharge will send a user_unsubscribed webhook. Recommended: add an “Email Unsubscribed” tag to the Kommo contact so SDRs know email marketing is unavailable for this lead and can focus on calls.
If you need a Kommo + Encharge integration - describe your stack and scenario to the Exceltic.dev team. We’ll work through the architecture in one meeting.