Kommo + Mixpanel: события воронки продаж в продуктовую аналитику

Kommo + Mixpanel: события воронки продаж в продуктовую аналитику

Mixpanel — платформа продуктовой аналитики с серверным Python SDK: события из любого источника отправляются через track(), профили пользователей — через people_set(). Без интеграции с Kommo Mixpanel видит только поведение в продукте, но не знает когда лид стал клиентом и сколько дней провёл в воронке. С интеграцией — события смены этапов, Won и Lost из CRM попадают в Mixpanel, и аналитик видит сквозную картину от первого касания до оплаты.

Зачем соединять Kommo и Mixpanel

Без интеграции:
— Mixpanel показывает retention, activation, feature usage — но не знает кто из пользователей стал платящим клиентом
— В Kommo есть данные о сделках, суммах, длине цикла — но нет связи с продуктовым поведением
— Product manager не может ответить: «Клиенты которые дошли до Won — они на каком этапе онбординга были?»
— Атрибуция выручки по маркетинговым каналам в Mixpanel не замыкается на реальные сделки

С интеграцией:
— Новый контакт в Kommo -> профиль в Mixpanel с CRM-свойствами (источник, менеджер, тип)
— Смена этапа -> событие Lead Stage Changed с именем этапа и ID сделки
— Won -> событие Deal Won с суммой, длиной цикла, источником
— Lost -> событие Deal Lost с причиной из Kommo
— В Mixpanel строятся воронки, когортный анализ и retention по реальным сделкам

Что синхронизируется

Kommo -> Mixpanel:
— Новый контакт -> people_set с email, компанией, источником лида, ответственным менеджером
— Смена этапа -> track('Lead Stage Changed', {stage, deal_id, deal_value})
— Won -> track('Deal Won', {revenue, cycle_days, source, manager})
— Lost -> track('Deal Lost', {reason, stage_at_loss, cycle_days})
— Создание задачи на контакте -> опционально track('CRM Task Created')

Mixpanel -> Kommo (опционально):
— Когорта «Активные пользователи» из Mixpanel -> тег на контакте в Kommo для приоритизации менеджером
— Спад активности в продукте -> Note в Kommo для превентивного звонка CSM

Архитектура

Kommo Webhook: контакт создан / обновлён
  ↓ Backend
  1. GET /api/v4/contacts/{id}
     -> email (как distinct_id), имя, компания, источник из кастомных полей
  2. Mixpanel: people_set(distinct_id=email, properties)
     -> $email, $name, crm_source, kommo_contact_id

Kommo Webhook: сделка изменена (этап / статус)
  ↓ Backend
  1. GET /api/v4/leads/{id} + contacts
     -> этап, сумма, контакт, дата создания
  2. distinct_id = email контакта
  3. Mixpanel: track(distinct_id, event_name, properties)
     -> Для Won: 'Deal Won' + {revenue, cycle_days, source, manager}
     -> Для Lost: 'Deal Lost' + {reason, stage_at_loss}
     -> Для смены этапа: 'Lead Stage Changed' + {from_stage, to_stage, deal_id}

Mixpanel Python SDK: ключевые запросы

pip install mixpanel

Инициализация (с поддержкой EU):

from mixpanel import Mixpanel, Consumer

MP_TOKEN = 'your_project_token'
MP_SECRET = 'your_api_secret'  # для import_data

# EU data residency - обязательно для EU-рынков (GDPR)
mp = Mixpanel(MP_TOKEN, consumer=Consumer(api_host='api-eu.mixpanel.com'))

# US (по умолчанию)
# mp = Mixpanel(MP_TOKEN)

Создать/обновить профиль контакта:

def sync_contact_to_mixpanel(email: str, name: str, company: str,
                              source: str, kommo_id: int):
    mp.people_set(email, {
        '$email': email,
        '$name': name,
        'company': company,
        'crm_source': source,
        'kommo_contact_id': kommo_id
    })

Отправить событие смены этапа:

from datetime import datetime, date

def track_stage_change(email: str, deal_id: int, deal_name: str,
                        from_stage: str, to_stage: str, deal_value: float):
    mp.track(email, 'Lead Stage Changed', {
        'deal_id': deal_id,
        'deal_name': deal_name,
        'from_stage': from_stage,
        'to_stage': to_stage,
        'deal_value': deal_value
    })

def track_deal_won(email: str, deal_id: int, revenue: float,
                   source: str, manager: str, created_at: datetime):
    cycle_days = (date.today() - created_at.date()).days
    mp.track(email, 'Deal Won', {
        'deal_id': deal_id,
        'revenue': revenue,
        'source': source,
        'manager': manager,
        'cycle_days': cycle_days
    })
    # Обновить профиль - пометить как платящего клиента
    mp.people_set(email, {
        'customer_status': 'paying',
        'first_purchase_date': date.today().isoformat(),
        'ltv': revenue
    })

def track_deal_lost(email: str, deal_id: int, reason: str,
                    stage_at_loss: str, created_at: datetime):
    cycle_days = (date.today() - created_at.date()).days
    mp.track(email, 'Deal Lost', {
        'deal_id': deal_id,
        'loss_reason': reason,
        'stage_at_loss': stage_at_loss,
        'cycle_days': cycle_days
    })

Импорт исторических данных (события старше 5 дней):

def import_historical_event(email: str, event_name: str,
                             timestamp: int, properties: dict):
    mp.import_data(
        api_secret=MP_SECRET,
        distinct_id=email,
        event_name=event_name,
        timestamp=timestamp,  # Unix timestamp
        properties=properties
    )

import_data() нужен при первичном наполнении — когда загружается история сделок из Kommo за последние месяцы. В реальном времени используется track().

Webhook-обработчик Kommo:

from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/kommo', methods=['POST'])
def kommo_webhook():
    data = request.json
    event_type = data.get('leads', {}).get('status')

    if event_type:
        for lead in event_type:
            deal_id = lead.get('id')
            # Получить полные данные сделки и контакта
            deal = get_kommo_lead(deal_id)
            contact = get_lead_contact(deal)

            if not contact or not contact.get('email'):
                continue  # без email distinct_id не определить

            handle_deal_event(deal, contact)

    return '', 200

def handle_deal_event(deal: dict, contact: dict):
    email = contact['email']
    status_id = deal.get('status_id')

    if status_id == 142:  # Won (стандартный ID в Kommo)
        track_deal_won(email, deal['id'], deal.get('price', 0),
                       deal.get('source', ''), deal.get('manager', ''),
                       deal['created_at'])
    elif status_id == 143:  # Lost
        loss_reason = deal.get('loss_reason', {}).get('name', '')
        track_deal_lost(email, deal['id'], loss_reason,
                        deal.get('stage_name', ''), deal['created_at'])
    else:
        track_stage_change(email, deal['id'], deal.get('name', ''),
                           deal.get('prev_stage', ''), deal.get('stage_name', ''),
                           deal.get('price', 0))

Distinct ID: email как универсальный идентификатор

Mixpanel использует distinct_id для связи профиля и событий. В серверной интеграции с Kommo проще всего использовать email контакта — он есть в CRM и уже известен Mixpanel если контакт ранее взаимодействовал с продуктом.

Проблема: если контакт зарегистрировался в продукте с одним email, а в Kommo записан с другим — профили разделятся. Решение: $merge через Mixpanel Identity API, или строгая политика использования единого email на всех платформах.

Если продукт использует Mixpanel JS SDK и distinct_id генерируется автоматически (anonymous ID) — в Kommo нужно хранить этот ID в кастомном поле и передавать в события вместо email.

Реальный кейс

B2B SaaS (EU, 150–200 триалов в месяц, продуктовая и sales команды работают независимо):

  • До: Mixpanel показывал activation funnel, но product team не могли ответить «сколько активированных пользователей стали платящими». Kommo хранил Won-сделки, но без связи с продуктовым поведением.
  • После: каждый Won из Kommo -> событие в Mixpanel с cycle_days и revenue. В Mixpanel built воронка: Trial Start -> Activation -> Deal Won. Выяснилось: пользователи прошедшие онбординг за <3 дня конвертируются в 2.3x чаще — это стало приоритетом product team.
  • Дополнительно: когорта «чернил активность за 14 дней» из Mixpanel -> Note в Kommo -> CSM делает проактивный звонок до оттока.

Для глубокой SQL-аналитики по данным Kommo без Mixpanel — смотрите Redash. Для атрибуции рекламных каналов до закрытой сделки — Prooflytics даёт более точную картину чем Mixpanel.

Для кого актуально

  • Компания использует Mixpanel для продуктовой аналитики и Kommo для продаж
  • Product team хочет видеть какие паттерны поведения в продукте коррелируют с Won
  • Sales team хочет получать сигналы из Mixpanel (спад активности, достижение лимитов) в Kommo
  • Нужна когортная аналитика по циклу сделки: сколько дней от первого касания до оплаты

Часто задаваемые вопросы

Mixpanel track() или import_data() — когда что использовать?

track() — для событий в реальном времени: принимает события не старше 5 дней. import_data() — для исторических данных: любой timestamp, требует api_secret (не токен), использует endpoint /import. При первичном наполнении используйте import_data для истории, затем переключайтесь на track для ongoing событий.

Как настроить EU data residency в Mixpanel?

При инициализации SDK передать кастомный Consumer: Mixpanel(token, consumer=Consumer(api_host='api-eu.mixpanel.com')). Без этого данные пойдут на US-серверы — нарушение GDPR для EU-контактов. EU residency настраивается на уровне проекта в Mixpanel UI и должна совпадать с конфигурацией SDK.

Что использовать как distinct_id?

Для серверной интеграции с CRM — email контакта: он доступен в Kommo и позволяет объединить CRM-профиль с продуктовым. Если в вашем продукте distinct_id генерируется клиентом (anonymous UUID при первом визите) — нужно хранить его в кастомном поле Kommo и передавать в события. Email как distinct_id — проще, но требует единой политики email на всех платформах.

Mixpanel принимает события из бэкенда без SDK?

Да. Можно отправлять события напрямую через HTTP API: POST https://api.mixpanel.com/track (или api-eu.mixpanel.com/track) с base64-encoded JSON. Python SDK делает это автоматически — рекомендуется использовать SDK, а не raw HTTP, для корректной обработки ошибок и retry.

Нужен ли токен или secret для track()?

track() и people_set() используют project token (публичный, безопасно хранить в коде). import_data() требует API secret (приватный, хранить в переменных окружения). Оба значения доступны в Mixpanel: Settings -> Project Settings.

Итого

  • Mixpanel Python SDK: pip install mixpanel, EU consumer для api-eu.mixpanel.com
  • people_set() — профиль контакта из Kommo; track() — события этапов, Won, Lost
  • import_data() с api_secret — для первичной загрузки истории сделок
  • distinct_id = email контакта — простейший вариант для CRM-интеграции
  • В Mixpanel строятся воронки по CRM-событиям и когортный анализ цикла сделки
  • Типовой срок разработки — 1–2 недели

Если вы используете Mixpanel и Kommo и хотите видеть сквозную картину от первого касания до закрытой сделки — опишите вашу структуру: какие события важны, какие свойства сделок нужны в аналитике. Exceltic.dev настроит маппинг и первичный импорт истории.

Ещё статьи

Все →