Legan Studio
Все статьи
~ 13 мин чтения

Аналитика Telegram-бота: что и как замерять

Разбираем, какие метрики реально полезны для Telegram-бота: воронка, ретеншн, когорты, источники трафика и интеграции с BI.

  • Telegram
  • аналитика
  • конверсия

Бот без аналитики — это «чёрный ящик»: трафик идёт, заявки приходят, но непонятно, где течёт воронка и откуда лучшие клиенты. На уровне «количество подписчиков» далеко не уехать. Реальная польза — в правильно собранной модели событий, воронках и когортах. Разберём, как построить аналитику Telegram-бота от нуля до зрелого продукта: что мерить, где хранить, как считать ретеншн, как разрезать по источникам и как не утонуть в мусорных данных.

Большинство команд проходит три стадии: «подсчёт подписчиков» → «таблица событий + базовые воронки» → «полноценный продуктовый стек с когортами, A/B и алертами». Перепрыгнуть стадии нельзя — но можно не задерживаться на первой дольше пары спринтов.

Что вообще можно мерить в Telegram-боте

Telegram даёт три уровня данных:

  1. Bot API — события, которые приходят в виде Update: сообщения, callback, inline-запросы, успешные платежи, изменение chat_member. Это сырьё.
  2. Mini App / WebApp — полноценная веб-аналитика внутри iframe: Yandex Metrika, GA4, Amplitude, PostHog. Здесь всё как в обычном вебе плюс initData пользователя.
  3. Внешние системы — CRM (сделки, выручка), платёжный шлюз (refund, MRR), биллинг подписок, поддержка (тикеты, время ответа).

Хорошая аналитика склеивает все три уровня по user_id (для бота это tg_user_id) и даёт цельную картину: пришёл с рекламы → нажал /start → дошёл до чекаута → купил → продлил подписку через месяц.

Список ключевых метрик

Минимальный набор для большинства ботов:

МетрикаЧто показываетКогда смотреть
DAU / WAU / MAUАктивная аудиторияЕжедневно
Retention D1 / D7 / D30Возвращаемость по когортамЕженедельно
Conversion воронкиУзкие местаПосле каждого релиза
Session lengthГлубина вовлеченияЕженедельно
Command frequencyКакие команды живыеЕжемесячно
CTR inline-кнопокКачество UXПосле A/B
NPSУдовлетворённостьРаз в квартал
ARPU / LTVДеньги на пользователяЕжемесячно
CAC по источникамЭффективность каналовЕженедельно
Доля 403 ForbiddenСколько заблокировали ботЕжедневно

DAU/MAU — базовая метрика, но без воронок она бесполезна: можно гнать трафик и держать DAU, теряя при этом 95% на первом шаге.

Слой событий — фундамент

Всё начинается с продуктовых событий. Это не «логи Bot API», а явный список действий пользователя:

  • start — пользователь нажал /start;
  • quiz_step_completed — прошёл шаг квиза;
  • cart_item_added — добавил товар в корзину;
  • checkout_started — открыл чекаут;
  • payment_succeeded — заплатил;
  • support_escalated — попросил оператора.

Каждое событие — это запись с user_id, timestamp, properties (UTM, сегмент, цена, шаг). Эти данные складываются в собственную таблицу или продуктовую аналитику (Amplitude, Mixpanel, PostHog, Yandex Metrika+).

Без явной модели событий любая последующая аналитика рассыпается.

Схема таблицы событий

Минимальная схема в PostgreSQL/ClickHouse — event_name, user_id, timestamp, properties JSON. Расширения добавляются по мере роста: session_id, device, app_version, experiment_id.

CREATE TABLE events (
  id           BIGSERIAL PRIMARY KEY,
  event_name   TEXT NOT NULL,
  user_id      BIGINT NOT NULL,
  session_id   UUID,
  ts           TIMESTAMPTZ NOT NULL DEFAULT now(),
  source       TEXT,
  properties   JSONB NOT NULL DEFAULT '\{\}'::jsonb
);

CREATE INDEX events_user_ts_idx  ON events (user_id, ts);
CREATE INDEX events_name_ts_idx  ON events (event_name, ts);
CREATE INDEX events_props_gin    ON events USING GIN (properties);

В коде бота заводится единственный helper track(event, props), который пишет строку в БД (или шлёт в очередь). Прямые INSERT-ы из бизнес-логики запрещены — иначе схема расползётся за месяц.

async function track(
  userId: number,
  eventName: string,
  properties: Record<string, unknown> = {}
) {
  await db.query(
    `INSERT INTO events (user_id, event_name, source, properties)
     VALUES ($1, $2, $3, $4)`,
    [userId, eventName, properties.source ?? null, properties]
  );
}

bot.command("start", async (ctx) => {
  const utm = parseStartPayload(ctx.match);
  await upsertUser(ctx.from.id, { utm });
  await track(ctx.from.id, "start", { utm, lang: ctx.from.language_code });
  await ctx.reply("Привет!");
});

Воронки — главное оружие

Воронка показывает, какой процент пользователей проходит через цепочку шагов. Базовые воронки для бота:

  • Активация: start → согласие на обработку ПДн → email/телефон → первое полезное действие.
  • Покупка: start → каталог → корзина → чекаут → payment_succeeded.
  • Подписка: start → выбор тарифа → оплата → активная подписка → продление.
  • Поддержка: обращение → ответ бота → решение / эскалация на оператора.

Когда видна структура, понятно, где «обрыв». Если 40% пользователей открывают чекаут, но только 5% платят — проблема в чекауте, а не в трафике.

UTM в /start — единственный способ разделить источники

Любая ссылка на бот — это t.me/your_bot?start=PAYLOAD. Параметр start идеально подходит для UTM, но на полноценную CSV-строку места не хватает: payload ограничен 64 символами и допускает только [A-Za-z0-9_-]. Поэтому используют либо короткие коды, либо base64url.

import { Buffer } from "node:buffer";

type Utm = {
  source?: string;
  medium?: string;
  campaign?: string;
  content?: string;
};

function encodeUtm(utm: Utm): string {
  const json = JSON.stringify(utm);
  return Buffer.from(json).toString("base64url").slice(0, 60);
}

function parseStartPayload(payload?: string): Utm {
  if (!payload) return {};
  try {
    const json = Buffer.from(payload, "base64url").toString("utf8");
    return JSON.parse(json) as Utm;
  } catch {
    return { campaign: payload };
  }
}

const link = `https://t.me/your_bot?start=${encodeUtm({
  source: "yandex",
  medium: "cpc",
  campaign: "brand_ru",
})}`;

При первом контакте бот сохраняет UTM в профиле пользователя (users.first_utm), и дальше любая метрика разворачивается по источникам. Это позволяет считать CAC и ROMI по каждому каналу, а не «в среднем по больнице».

Когорты и ретеншн

Когортный анализ показывает, что происходит с пользователями со временем. Стандартные когорты:

  • по дате регистрации;
  • по источнику (рекламный канал, реферал);
  • по сегменту (новый, активный, спящий);
  • по первому действию (купил сразу vs только смотрел).

Главная метрика — ретеншн на день N (доля пользователей, вернувшихся в бот через 1, 7, 14, 30 дней). Для подписочных продуктов ещё считается MRR-ретеншн и net revenue retention.

Простой SQL-запрос для D7-ретеншна по неделе регистрации:

WITH cohorts AS (
  SELECT
    user_id,
    DATE_TRUNC('week', MIN(ts))::date AS cohort_week
  FROM events
  WHERE event_name = 'start'
  GROUP BY user_id
),
returns AS (
  SELECT
    c.cohort_week,
    c.user_id,
    MAX(CASE
      WHEN e.ts BETWEEN c.cohort_week + INTERVAL '7 day'
                    AND c.cohort_week + INTERVAL '8 day'
      THEN 1 ELSE 0
    END) AS returned_d7
  FROM cohorts c
  LEFT JOIN events e USING (user_id)
  GROUP BY c.cohort_week, c.user_id
)
SELECT
  cohort_week,
  COUNT(*)                                AS users,
  ROUND(AVG(returned_d7)::numeric, 3)     AS retention_d7
FROM returns
GROUP BY cohort_week
ORDER BY cohort_week DESC;

Главное правило когорт: смотреть не средние числа, а строки таблицы. Средний ретеншн часто врёт — новые когорты «вытягивают» статистику, пока старые отваливаются.

A/B-тесты на пользователях бота

Расщепление обычно делается по hash от user_id — это даёт стабильную группу для каждого пользователя на всё время эксперимента.

import { createHash } from "node:crypto";

function bucket(userId: number, experiment: string, variants = 2): number {
  const hash = createHash("sha256")
    .update(`${experiment}:${userId}`)
    .digest();
  return hash.readUInt32BE(0) % variants;
}

function variant(userId: number) {
  const v = bucket(userId, "checkout_copy_v3", 2) === 0 ? "A" : "B";
  // фиксируем exposure — без него тест нечестный
  void track(userId, "experiment_exposure", {
    experiment: "checkout_copy_v3",
    variant: v,
  });
  return v;
}

Без события experiment_exposure (фиксации показа) тест нечестный: посчитать конверсию можно только по тем, кто реально увидел вариант. Для оценки значимости используется z-test для пропорций или bootstrap — в Metabase для этого достаточно одного SQL-запроса с STDDEV и SQRT(p*(1-p)/n).

Сравнение аналитических систем

ИнструментСильные стороныМинусыДля кого
Своя таблица + MetabaseПолный контроль, дёшевоНадо строить рукамиВсе, у кого есть бэкенд
ClickHouse + SupersetМиллионы ивентов в деньСложнее эксплуатацияHigh-load
AmplitudeГотовые воронки, когортыДорого, данные вне РФЗрелый продукт
MixpanelГибкие отчётыДорого, данные вне РФПродуктовые команды
PostHog (self-hosted)Open-source, можно в РФНадо администрироватьКоманды с DevOps
Yandex MetrikaБесплатно, цели, вебвизорТолько для Mini AppMini App с веб-частью
GA4Универсальный стандартСложный, данные вне РФMini App с глобальной аудиторией
BotAnalytics / DAUMЗаточено под TelegramУзкий функционалMVP, быстрый старт

Для большинства проектов «PostgreSQL + Metabase + Yandex Metrika для Mini App» закрывает 80% задач без больших затрат. ClickHouse подключают, когда событий становится больше 10–20 млн в сутки.

Дашборды и BI

Куда складывать графики:

  • Metabase — самый простой старт, SQL-редактор, бесплатная open-source-версия.
  • Apache Superset — мощнее Metabase, но сложнее в эксплуатации.
  • Grafana — хороша для технических метрик (latency, ошибки), хуже для продуктовых.
  • Yandex DataLens — российский SaaS, бесплатный лимит, удобен для команд в РФ.
  • Power BI / Tableau — корпоративный сегмент, дорого.

Базовый набор дашбордов: «Главная» (DAU/MAU, конверсия, выручка), «Воронки», «Когорты», «Источники», «Ошибки». Каждый дашборд должен помещаться на один экран — иначе им никто не пользуется.

ClickHouse, когда событий очень много

Когда поток превышает несколько миллионов событий в сутки, PostgreSQL начинает тормозить на агрегациях. Стандартный путь — складывать сырые события в ClickHouse через Kafka или прямую запись батчами.

CREATE TABLE events
(
  event_date    Date          DEFAULT toDate(ts),
  ts            DateTime64(3),
  event_name    LowCardinality(String),
  user_id       UInt64,
  source        LowCardinality(String),
  properties    String CODEC(ZSTD(3))
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_name, user_id, ts)
TTL event_date + INTERVAL 18 MONTH;

LowCardinality для имени события и источника даёт 5–10× экономии места, ZSTD сжимает JSON-properties. Запросы на годовых данных отвечают за секунды вместо часов.

Алерты на бизнес-метрики

Технические алерты (5xx, latency) обычно уже есть. Для бизнеса нужны отдельные:

  • Активация упала ниже 35% за последний час → проблема с регистрацией или согласием.
  • Конверсия в оплату упала на 30% относительно недели → сломался платёжный шлюз.
  • Доля 403 Forbidden в sendMessage выросла → массовая блокировка после рассылки.
  • DAU ниже скользящего среднего на 2σ → внешний инцидент или баг.

Алерты живут в Grafana / Yandex Monitoring / Prometheus Alertmanager. Главное правило: алерт обязан вести к действию. Если на него нечего ответить — это не алерт, а шум, его надо удалить.

Связка с CRM и деньгами

Метрики бота особенно ценны, когда привязаны к деньгам. Это требует связки с CRM:

  • сделка из CRM → событие deal_won или deal_lost;
  • сумма сделки → revenue по пользователю;
  • LTV считается по CRM, а не по «событию покупки в боте»;
  • refund из платёжки → payment_refunded, корректирует MRR.

Без такой связки маркетинговая аналитика обманывает: можно гнать дешёвый трафик в плохих лидов и считать его «выгодным» по числу заявок.

Privacy и 152-ФЗ

Аналитика — это работа с персональными данными. Минимальные требования для российских проектов:

  • Согласие на обработку ПДн до сбора phone, email, любых ID, кроме tg_user_id (он считается обезличенным до обогащения).
  • Уведомление РКН об обработке ПДн (если собирается что-то кроме обезличенного).
  • Уведомление о трансграничной передаче, если используется Amplitude/Mixpanel/GA4 (серверы вне РФ).
  • Право на удаление: должен быть механизм /forget или ручка в админке, которая удаляет пользователя из events и users.
  • Агрегирование: для большинства дашбордов хватит count, avg, sum без сырых ID.

Для Mini App дополнительно — баннер cookies (Yandex Metrika ставит ID в localStorage).

Анти-паттерны

Что точно не делать:

  • Tracking всего подряд без модели — данных много, выводов нет, schema превращается в data swamp.
  • Неконсистентные имена событийcart_add, addToCart, add_to_cart живут в одной таблице, и склеить их нельзя.
  • Нет dictionary событий — спустя полгода никто не помнит, что значит step_3_done.
  • Полагаться только на встроенную аналитику конструктора — закрытая, не разворачивается на BI.
  • Считать «количество подписчиков» как KPI — это vanity-метрика, ничего не предсказывает.
  • Делать выводы на 1–2 неделях данных без когорт.
  • Хранить ПДн в properties без необходимости — ставит под удар при утечке.
  • A/B-тест без exposure — приходится верить, что 50/50 действительно увидели обе версии.

Roadmap внедрения

Прагматичный порядок работ:

  1. Неделя 1–2: таблица events, helper track(), базовые события (start, ключевые шаги воронки, payment_succeeded). Поднять Metabase, нарисовать первые 3 графика.
  2. Неделя 3–4: UTM в /start, дашборд по источникам, базовый ретеншн D1/D7.
  3. Месяц 2: связка с CRM, LTV/CAC, алерты на ключевые метрики.
  4. Месяц 3: A/B-инфраструктура, dictionary событий, регулярные продуктовые ревью раз в неделю.
  5. Месяц 6+: ClickHouse, когортные дашборды, прогнозы LTV, ML-сегментация.

Перепрыгивать пункты дорого: без воронок не имеет смысла строить ML, без dictionary — внедрять A/B.

Итого

Аналитика Telegram-бота — это связка модели событий, воронок, когорт, ретеншна и UTM-разреза. Минимум — собственная таблица событий + Metabase + связка с CRM. Без аналитики невозможно понять, где рвётся воронка и какие каналы окупаются. Срок внедрения базового слоя — 1–3 недели, а отдача проявляется уже после первого месяца наблюдений. Главное — не тащить в трекинг всё подряд, а явно описать события, которые отвечают на вопросы продукта.

Частые вопросы

Какие события обязательно логировать в Telegram-боте?

Всё начинается с продуктовых событий — это не «логи Bot API», а явный список действий пользователя. Базовые: start (нажал /start), quiz_step_completed (прошёл шаг квиза), cart_item_added (добавил товар в корзину), checkout_started (открыл чекаут), payment_succeeded (заплатил), support_escalated (попросил оператора). Каждое событие — это запись с user_id, timestamp, properties (UTM, сегмент, цена, шаг). Складываются в собственную таблицу или продуктовую аналитику (Amplitude, Mixpanel, PostHog, Yandex Metrika+). Без явной модели событий любая последующая аналитика рассыпается.

Какие воронки считать для Telegram-бота?

Воронка показывает, какой процент пользователей проходит через цепочку шагов. Базовые воронки. Активация: start → согласие → email/телефон → первое полезное действие. Покупка: start → каталог → корзина → чекаут → payment_succeeded. Подписка: start → выбор тарифа → оплата → активная подписка → продление. Поддержка: обращение → ответ бота → решение / эскалация. Когда видна структура, понятно, где «обрыв». Если 40% пользователей открывают чекаут, но только 5% платят — проблема в чекауте, а не в трафике.

Как считать когорты и ретеншн в боте Telegram?

Когортный анализ показывает, что происходит с пользователями со временем. Стандартные когорты: по дате регистрации, по источнику (рекламный канал, реферал), по сегменту (новый, активный, спящий), по первому действию (купил сразу vs только смотрел). Главная метрика — ретеншн на день N (доля пользователей, вернувшихся в бот через 1, 7, 14, 30 дней). Для подписочных продуктов ещё считается MRR-ретеншн и net revenue retention. Без когортного разреза средние числа обманывают: новые пользователи могут «съедать» данные плохого retention старых.

Как реализовать UTM-метки в Telegram-боте?

Любая ссылка на бот — это t.me/your_bot?start=PAYLOAD. Параметр start идеально подходит для UTM, но ограничен 64 символами и допускает только латиницу/цифры/_/-. Поэтому используют короткие коды (start=tg_ads_jan, start=insta_blogger123) или base64url-кодирование JSON-объекта с source/medium/campaign/content. При первом контакте бот декодирует payload и сохраняет UTM в профиле пользователя, и дальше любая метрика разворачивается по источникам. Это позволяет считать CAC и ROMI по каждому каналу.

Какой минимальный набор метрик нужен для Telegram-бота?

Десять базовых. DAU/MAU — активная аудитория. Конверсия в первый отклик после /start. CR в основное целевое действие (заявка, покупка, подписка). AOV (средний чек) и LTV. CAC по источникам. Ретеншн D1/D7/D30. Churn (для подписок). Доля эскалаций на оператора. Процент ошибок Bot API и 403 Forbidden (бот заблокирован пользователем). Время ответа бота и время решения тикета (для саппорта). Этих метрик хватает для принятия решений, всё остальное — детали под конкретный продукт.

Какой технологический стек выбрать для аналитики бота?

Самые популярные варианты. PostgreSQL/ClickHouse — собственная таблица событий. Metabase / Apache Superset / Yandex DataLens — BI-дашборды. Amplitude / Mixpanel / PostHog — продуктовая аналитика. Yandex Metrika и GA4 — для Mini App. dbt — преобразование событий в витрины. Airflow / Cron — регулярные обновления. Для большинства проектов «PostgreSQL + Metabase + Yandex Metrika для Mini App» закрывает 80% задач без больших затрат. ClickHouse подключают, когда событий становится больше 10–20 млн в сутки.

Как правильно делать A/B-тесты в Telegram-боте?

Расщепление по hash от user_id (sha256(experiment:user_id) % N) — это даёт стабильную группу для каждого пользователя. Обязательно фиксировать exposure — событие experiment_exposure с experiment_id и variant в момент показа. Без него тест нечестный: считать конверсию можно только по тем, кто реально увидел вариант. Для оценки значимости — z-test для пропорций или bootstrap. Минимальный размер выборки считается заранее по ожидаемому эффекту и базовой конверсии. Не запускать A/B без чёткой гипотезы и метрики успеха.