Kommo + PostHog: Sales Pipeline Events in Product Analytics

PostHog is an open-source product analytics platform: event capture, session replay, feature flags, A/B tests, retention cohorts, and funnels. Self-hosted or cloud (EU/US). Without Kommo integration, the product team sees what users do in the product but does not know which of them became paying customers, on which plan, and through which acquisition channel. With the integration, a Won event from Kommo -> identify in PostHog -> retention cohort by plan, conversion funnel CRM -> activation, churn analysis by CRM attributes.

Why Connect CRM and Product Analytics

A typical problem: the product team analyses retention for all registered users. But retention of paying customers (Won in Kommo) and trial users are fundamentally different metrics. Without a CRM event, PostHog does not know when a user became a paying customer.

With Kommo integration, PostHog can see: — Retention only among customers on the Growth plan — Feature adoption among customers vs trial users — Time-to-activation for Won deals by acquisition channel — Churn indicators: CRM events before client loss

Compared to Kommo + Amplitude — PostHog is stronger for session replay and feature flags; Amplitude is stronger for predictive analytics and AI insights. For EU with self-hosting, PostHog is GDPR-compliant without sending data to a SaaS provider.

What Gets Synchronised

Kommo -> PostHog: — Won -> identify (update person properties: plan, mrr, source) — Won -> capture event deal_won — Plan change -> capture event plan_changed — Client churn -> capture event churned — Stage change -> capture event stage_changed

Additionally (group analytics for B2B): — Won -> group_identify for the client’s company (plan, mrr, team_size)

PostHog API: Key Requests

SDK (recommended):

pip install posthog
import posthog

# Cloud EU
posthog.project_api_key = "phc_your_project_api_key"
posthog.host = "https://eu.posthog.com"  # EU data residency

# Self-hosted
# posthog.host = "https://your-posthog-instance.com"

def on_deal_won(lead: dict, contact: dict):
    email = get_contact_email(contact)
    name = contact["name"]
    plan = get_custom_field(lead, PLAN_FIELD_ID) or "starter"
    source = get_custom_field(lead, SOURCE_FIELD_ID) or "direct"
    amount = lead.get("price", 0)
    company = get_custom_field(lead, COMPANY_FIELD_ID) or ""

    # identify - update person properties
    posthog.identify(
        distinct_id=email,
        properties={
            "plan": plan,
            "mrr": amount,
            "customer": True,
            "source": source,
            "kommo_deal_id": lead["id"],
            "name": name,
            "email": email,
        }
    )

    # capture - conversion event
    posthog.capture(
        distinct_id=email,
        event="deal_won",
        properties={
            "plan": plan,
            "amount": amount,
            "deal_id": lead["id"],
            "source": source,
            "pipeline": lead.get("pipeline_id"),
        }
    )

    # group analytics for B2B (company-level)
    if company:
        posthog.group_identify(
            group_type="company",
            group_key=company,
            properties={
                "name": company,
                "plan": plan,
                "mrr": amount,
            }
        )
        posthog.capture(
            distinct_id=email,
            event="deal_won",
            groups={"company": company}
        )

def on_stage_change(lead_id: int, old_stage: str, new_stage: str):
    lead = get_kommo_lead(lead_id)
    contact = get_kommo_contact(lead_id)
    email = get_contact_email(contact)

    posthog.capture(
        distinct_id=email,
        event="crm_stage_changed",
        properties={
            "from_stage": old_stage,
            "to_stage": new_stage,
            "deal_id": lead_id,
        }
    )

def on_deal_lost(lead: dict, contact: dict):
    email = get_contact_email(contact)
    loss_reason = get_custom_field(lead, LOSS_REASON_FIELD_ID) or "unknown"

    posthog.identify(
        distinct_id=email,
        properties={"customer": False, "churned": True, "plan": None}
    )
    posthog.capture(
        distinct_id=email,
        event="churned",
        properties={"loss_reason": loss_reason, "deal_id": lead["id"]}
    )

def on_plan_upgrade(lead: dict, contact: dict, old_plan: str, new_plan: str):
    email = get_contact_email(contact)
    posthog.identify(distinct_id=email, properties={"plan": new_plan})
    posthog.capture(
        distinct_id=email,
        event="plan_changed",
        properties={"from_plan": old_plan, "to_plan": new_plan}
    )

Flush for serverless/cron (important!):

# PostHog SDK buffers events - flush is needed before process termination
posthog.flush()
# Or enable synchronous sending:
posthog.sync_mode = True

HTTP API directly (without SDK):

import requests, uuid
from datetime import datetime, timezone

POSTHOG_API_KEY = "phc_your_api_key"
POSTHOG_HOST = "https://eu.posthog.com"

def ph_capture(distinct_id: str, event: str, properties: dict):
    requests.post(
        f"{POSTHOG_HOST}/capture/",
        json={
            "api_key": POSTHOG_API_KEY,
            "event": event,
            "distinct_id": distinct_id,
            "properties": properties,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "uuid": str(uuid.uuid4()),  # idempotency
        }
    )

Retention Cohorts Based on CRM Data

After integration, PostHog can be used to build:

Retention by plan: — Cohort: plan = "growth" (Person property) — Retention chart: deal_won (first action) -> logged_in (return) — Compare retention: Growth vs Starter vs Scale

Conversion funnel CRM -> activation:deal_won -> feature_used_first_time -> dashboard_created — See where clients drop off in the first 7 days

Churn predictor: — Find event patterns before churned — declining session count, feature abandonment — Use Cohort Analysis: clients with churned in the last 90 days -> their product behaviour 30 days before churn

Self-Hosted PostHog: EU GDPR

PostHog self-hosted (Docker / Kubernetes) — all data on your server, no external SaaS. For EU companies with GDPR requirements:

# docker-compose.yml (simplified)
version: '3'
services:
  posthog:
    image: posthog/posthog:latest
    environment:
      - SECRET_KEY=your_secret_key
      - DATABASE_URL=postgres://user:pass@db/posthog
      - REDIS_URL=redis://redis:6379/
    ports:
      - "8000:8000"

PostHog Cloud EU (eu.posthog.com) — data stays in Europe only, no self-hosting required. Sufficient for most EU teams.

Real-World Case

B2B SaaS (EU, Kommo + PostHog Cloud EU, 60–80 new clients per month):

  • Before: the product team could not separate paying customers from trial users in retention analysis. Retention of “all users” = 45%, “paying customers” = ? — unknown.
  • After: Won -> PostHog identify with plan. Retention of paying customers turned out to be 71%. Trial retention — 23%. Two fundamentally different products from a behavioural standpoint.
  • Additionally: the churned cohort revealed a pattern: 80% of churned clients had not used the key feature in the first 14 days. The onboarding flow was redesigned -> churn over 6 months decreased by 18%.

Who This Is Relevant For

  • Product teams that need retention segmentation by CRM attributes
  • SaaS with a long trial -> won journey: activation for paying customers needs to be measured separately
  • EU teams with GDPR requirements — PostHog self-hosted or EU cloud
  • Companies already using PostHog for their product and wanting to add CRM context

Frequently Asked Questions

PostHog vs Amplitude for Kommo integration?

Kommo + Amplitude — if you need predictive cohorts, AI recommendations, and SQL queries on data. PostHog — if you need open-source, self-hosted, session replay + analytics in one tool, EU hosting. The APIs are similar — the integration code is almost identical.

How do I merge PostHog anonymous_id with CRM email?

On Won — use alias if you have the anonymous_id from the browser session. If not — simply use email as distinct_id for CRM events. PostHog merging happens automatically if the user identified themselves via posthog.identify() in the browser with the same email.

Is PostHog project_api_key public or private?

project_api_key (phc_...) — public, used for capture and identify. It can be embedded in browser code. personal_api_key — private, used only for reading data via the Admin API. For the server-side Kommo integration, only the project_api_key is needed.

How do I comply with GDPR when passing email to PostHog?

PostHog EU cloud stores data only in the EU. For self-hosted — on your own server. Email as distinct_id is personal data: ensure the user has consented to product analytics during registration. PostHog supports $opt_out_capturing() for users who have opted out of tracking.

Summary

  • PostHog SDK: posthog.identify() + posthog.capture(), flush before process termination
  • EU cloud: posthog.host = "https://eu.posthog.com"
  • HTTP API: api_key in the request body (not in the header), uuid for idempotency
  • group_identify: for B2B company-level analytics
  • Key events: deal_won, churned, plan_changed, crm_stage_changed
  • Value: retention and funnels for paying customers separately from trial

If you use PostHog and Kommo and want to see CRM segments in retention analysis — describe your pricing structure and key events. Exceltic.dev will configure the integration.

More articles

All →