Kommo + Redash: Sales BI Dashboard on Your Own Data Without Vendor Lock-In

Kommo + Redash: Sales BI Dashboard on Your Own Data Without Vendor Lock-In

Redash is an open-source BI tool with a SQL interface, support for 35+ data sources, and built-in dashboard sharing. Unlike Superset (with its column and aggregation focus) or Metabase, Redash is built around direct SQL queries to data — an analyst writes a query, gets a visualisation, and publishes a dashboard. The Kommo integration is built through ETL: CRM data -> relational database -> Redash.

Redash vs Superset vs Metabase: When to Choose Redash

All three are open-source BI tools with self-hosting. The choice depends on the audience and usage pattern:

ParameterRedashApache SupersetMetabase
Primary interfaceSQL editorDrag-and-drop + SQLDrag-and-drop + SQL
Target audienceAnalysts, developersData engineersBusiness users
Entry barrierMedium (SQL required)HighLow
APIREST (query results, embeds)REST (Guest Token)REST
Self-hostingDocker, simpleDocker Compose, more complexDocker, simple
LicenseBSDApache 2.0AGPL / Commercial

Redash wins when analytics in the team means SQL queries rather than a GUI builder. For non-technical users — Metabase. For complex data engineering — Superset. A comparison with Apache Superset for Kommo analytics is in the Kommo + Apache Superset article.

Architecture: Kommo -> Postgres -> Redash

┌──────────────┐       ┌──────────────┐       ┌──────────────┐
│  Kommo API   │──ETL─▶│  PostgreSQL  │◀─SQL──│    Redash    │
│  /api/v4/    │       │  (your server)│      │  Dashboard   │
└──────────────┘       └──────────────┘       └──────────────┘
         ▲                                           │
         │                                     (embed / share)
         │                               ┌──────────────────────┐
         │                               │  Head of Sales       │
         └───────────────────────────────│  (browser or Slack)  │

The ETL script runs on a schedule (cron or Celery beat). Alternatives to Postgres: BigQuery, ClickHouse (better for large volumes), MySQL.

ETL: Exporting Kommo Data to Postgres

Table schema:

CREATE TABLE kommo_deals (
    id BIGINT PRIMARY KEY,
    name TEXT,
    status_id INTEGER,
    stage_name TEXT,
    pipeline_id INTEGER,
    responsible_user_id INTEGER,
    responsible_name TEXT,
    price DECIMAL(15,2),
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    closed_at TIMESTAMP,
    loss_reason TEXT
);

CREATE TABLE kommo_contacts (
    id BIGINT PRIMARY KEY,
    name TEXT,
    email TEXT,
    company TEXT,
    created_at TIMESTAMP
);

CREATE TABLE kommo_deal_contacts (
    deal_id BIGINT,
    contact_id BIGINT,
    PRIMARY KEY (deal_id, contact_id)
);

ETL script (incremental load):

import requests
from datetime import datetime, timedelta
import psycopg2

KOMMO_DOMAIN = 'yourcompany'
KOMMO_TOKEN = 'your_token'
PG_DSN = 'postgresql://user:pass@localhost/analytics'

def fetch_kommo_leads(updated_since: datetime):
    params = {
        'limit': 250,
        'with': 'contacts',
        'filter[updated_at][from]': int(updated_since.timestamp())
    }
    page = 1
    while True:
        response = requests.get(
            f'https://{KOMMO_DOMAIN}.kommo.com/api/v4/leads',
            headers={'Authorization': f'Bearer {KOMMO_TOKEN}'},
            params={**params, 'page': page}
        )
        if response.status_code == 204:  # no data
            break
        data = response.json()
        leads = data.get('_embedded', {}).get('leads', [])
        if not leads:
            break
        yield from leads
        page += 1

def upsert_deal(conn, lead: dict):
    with conn.cursor() as cur:
        cur.execute('''
            INSERT INTO kommo_deals
                (id, name, status_id, price, created_at, updated_at, responsible_user_id)
            VALUES (%s, %s, %s, %s, to_timestamp(%s), to_timestamp(%s), %s)
            ON CONFLICT (id) DO UPDATE SET
                status_id = EXCLUDED.status_id,
                price = EXCLUDED.price,
                updated_at = EXCLUDED.updated_at
        ''', (
            lead['id'], lead['name'], lead['status_id'],
            lead.get('price', 0),
            lead['created_at'], lead['updated_at'],
            lead.get('responsible_user_id')
        ))

def run_etl():
    conn = psycopg2.connect(PG_DSN)
    updated_since = datetime.now() - timedelta(hours=1)  # incremental: last hour
    for lead in fetch_kommo_leads(updated_since):
        upsert_deal(conn, lead)
    conn.commit()
    conn.close()

Redash: Key SQL Queries for the Sales Dashboard

Pipeline conversion by stage:

SELECT
    stage_name,
    COUNT(*) AS deals_count,
    SUM(price) AS pipeline_value,
    AVG(price) AS avg_deal_size,
    ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) AS pct_of_total
FROM kommo_deals
WHERE created_at >= NOW() - INTERVAL '30 days'
  AND status_id NOT IN (142, 143)  -- exclude Won/Lost for pipeline
GROUP BY stage_name
ORDER BY deals_count DESC;

Closed deal dynamics by week:

SELECT
    DATE_TRUNC('week', closed_at) AS week,
    responsible_name AS manager,
    COUNT(*) FILTER (WHERE status_id = 142) AS won_count,
    SUM(price) FILTER (WHERE status_id = 142) AS won_revenue,
    COUNT(*) FILTER (WHERE status_id = 143) AS lost_count
FROM kommo_deals
WHERE closed_at >= NOW() - INTERVAL '12 weeks'
GROUP BY 1, 2
ORDER BY 1 DESC, 3 DESC;

Win Rate by manager:

SELECT
    responsible_name,
    COUNT(*) FILTER (WHERE status_id = 142) AS won,
    COUNT(*) FILTER (WHERE status_id = 143) AS lost,
    ROUND(100.0 *
        COUNT(*) FILTER (WHERE status_id = 142) /
        NULLIF(COUNT(*) FILTER (WHERE status_id IN (142, 143)), 0)
    , 1) AS win_rate_pct,
    SUM(price) FILTER (WHERE status_id = 142) AS total_revenue
FROM kommo_deals
WHERE closed_at >= NOW() - INTERVAL '90 days'
GROUP BY responsible_name
ORDER BY win_rate_pct DESC;

Redash REST API: Fetching Query Results

Redash provides a REST API for programmatic access to results. Used for integrations (e.g. sending data to Slack):

REDASH_URL = 'https://redash.yourcompany.com'
REDASH_API_KEY = 'your_user_api_key'  # from Settings -> API Key

def get_query_results(query_id: int) -> dict:
    # First trigger (refresh) the query
    requests.post(
        f'{REDASH_URL}/api/queries/{query_id}/refresh',
        headers={'Authorization': f'Key {REDASH_API_KEY}'}
    )
    # Fetch results
    response = requests.get(
        f'{REDASH_URL}/api/queries/{query_id}/results.json',
        headers={'Authorization': f'Key {REDASH_API_KEY}'}
    )
    return response.json()['query_result']['data']['rows']

# Example: get top 3 managers for the week and send to Slack
rows = get_query_results(WEEKLY_MANAGERS_QUERY_ID)
for row in rows[:3]:
    print(f"{row['responsible_name']}: {row['won_count']} deals")

Redash Embed in Slack/Notion

Redash supports publishing dashboards via a link. For internal use — without additional authentication via ?api_key= in the iframe URL.

Weekly automatic report in Slack:

import schedule

def send_weekly_report():
    rows = get_query_results(WEEKLY_SUMMARY_QUERY_ID)
    text_lines = [f'*Weekly Summary:*']
    for row in rows:
        text_lines.append(
            f"{row['responsible_name']}: {row['won_count']} Won, "
            f"${row['won_revenue']:,.0f} revenue"
        )
    text_lines.append(f'\n<{REDASH_URL}/dashboard/sales|Open dashboard>')

    requests.post(SLACK_WEBHOOK_URL, json={'text': '\n'.join(text_lines)})

schedule.every().monday.at('09:00').do(send_weekly_report)

Real-World Case

B2B SaaS company (30 employees, EU market, Kommo + self-hosted infrastructure):

  • Before: the head of sales manually requested reports from managers every Monday. Data arrived in different formats and took 2–3 hours to consolidate.
  • After: ETL script synchronises Kommo -> Postgres every hour. Redash dashboard with pipeline conversion, win rate by manager, and revenue dynamics updates in real time. Weekly report delivered to Slack every Monday at 9:00 automatically.
  • Why Redash: the team is technically proficient, the analyst writes SQL — Redash is a perfect fit. Metabase was considered too UI-heavy, Superset too complex to configure.

A similar pattern with Apache Superset is in the Kommo + Superset article: more powerful visualisations, but a higher configuration threshold.

Who This Is Relevant For

  • The team has an analyst or developer who writes SQL
  • Self-hosted BI is needed without Looker, Tableau, or Metabase Cloud
  • Data control is important — no third-party clouds with client data
  • Goal: dashboards for the head of sales with conversion, win rate, and pipeline by manager
  • Kommo’s built-in reports do not provide enough flexibility

Frequently Asked Questions

How complex is Redash self-hosted to deploy?

Redash is deployed via Docker Compose in 15–30 minutes on any VPS. The official docker-compose.yml includes Postgres (for Redash metadata), Redis, and the server itself. The main challenge is configuring the connection to your Postgres with Kommo data and issuing an SSL certificate for the domain.

How often should data be synchronised from Kommo?

For operational dashboards (managers check throughout the day) — every 15–30 minutes. For weekly management reports — once an hour or on a schedule. The Kommo API supports updated_at filtering — incremental load does not stress the API or the database.

Can Redash connect directly to Kommo without a database?

No. Redash works with SQL-compatible sources: Postgres, MySQL, BigQuery, ClickHouse, etc. The Kommo API is not a SQL source. An intermediate database is mandatory — this is part of the architecture.

Redash vs Google Data Studio (Looker Studio) for Kommo analytics?

Looker Studio — cloud tool, no infrastructure required, easier for non-technical users. Redash — self-hosted, full data control, SQL-oriented. If client data cannot leave the EU perimeter — Redash. If you need quick visualisation without DevOps — Looker Studio.

Is paid Redash required?

Redash is fully open-source (BSD licence). Commercial support from airSlate is available, but for most companies the self-hosted version is sufficient. The hosted version (redash.io) was shut down — only self-hosting is now available.

Summary

  • Pattern: ETL Kommo API -> Postgres (incremental, every 15–30 min) -> Redash SQL queries -> dashboards
  • Key metrics: pipeline conversion, win rate by manager, weekly revenue dynamics
  • Redash REST API: GET /api/queries/{id}/results.json — fetch results for external integrations (Slack reports)
  • Self-hosted on Docker Compose: full data control, no vendor lock-in
  • Typical timeline: ETL + schema — 1 week, dashboards — another 1 week

If you have Kommo and want self-hosted BI without Tableau and Looker — describe what metrics the head of sales needs. Exceltic.dev will design the database schema and key dashboard queries.

More articles

All →