Бот без аналитики — это «чёрный ящик»: трафик идёт, заявки приходят, но непонятно, где течёт воронка и откуда лучшие клиенты. На уровне «количество подписчиков» далеко не уехать. Реальная польза — в правильно собранной модели событий, воронках и когортах. Разберём, как построить аналитику Telegram-бота от нуля до зрелого продукта: что мерить, где хранить, как считать ретеншн, как разрезать по источникам и как не утонуть в мусорных данных.
Большинство команд проходит три стадии: «подсчёт подписчиков» → «таблица событий + базовые воронки» → «полноценный продуктовый стек с когортами, A/B и алертами». Перепрыгнуть стадии нельзя — но можно не задерживаться на первой дольше пары спринтов.
Что вообще можно мерить в Telegram-боте
Telegram даёт три уровня данных:
- Bot API — события, которые приходят в виде Update: сообщения, callback, inline-запросы, успешные платежи, изменение
chat_member. Это сырьё. - Mini App / WebApp — полноценная веб-аналитика внутри iframe: Yandex Metrika, GA4, Amplitude, PostHog. Здесь всё как в обычном вебе плюс
initDataпользователя. - Внешние системы — 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 App | Mini 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–2: таблица
events, helpertrack(), базовые события (start, ключевые шаги воронки,payment_succeeded). Поднять Metabase, нарисовать первые 3 графика. - Неделя 3–4: UTM в
/start, дашборд по источникам, базовый ретеншн D1/D7. - Месяц 2: связка с CRM, LTV/CAC, алерты на ключевые метрики.
- Месяц 3: A/B-инфраструктура, dictionary событий, регулярные продуктовые ревью раз в неделю.
- Месяц 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 без чёткой гипотезы и метрики успеха.