«Сколько у нас пользователей в боте?» — самый бесполезный вопрос про продукт. Большое число подписчиков ничего не говорит ни о выручке, ни о здоровье бота, ни о том, удерживает ли он аудиторию. Разберём, какие метрики действительно показывают, как работает бот в MAX (мессенджер VK Tech), как собирать события, строить воронки и когорты, и какие цифры — пустой шум, который только мешает принимать решения.
Большинство команд проходит три стадии зрелости аналитики: «считаем подписчиков» → «таблица событий + базовые воронки» → «полноценный продуктовый стек с когортами, A/B-тестами и алертами на бизнес-метрики». Перепрыгнуть стадии нельзя, но и задерживаться на первой дольше пары спринтов — значит работать вслепую.
Что вообще можно мерить в боте MAX
MAX как платформа даёт три уровня данных:
- Bot API — события, которые приходят от платформы: входящие сообщения, нажатия inline-кнопок (callback), подключение/отключение бота, изменения членства в чатах, события платежей. Это сырьё.
- Mini App / WebView — полноценная веб-аналитика внутри встроенного браузера: Yandex Metrika, Amplitude, PostHog. Здесь работают те же инструменты, что и в обычном вебе, плюс доступ к данным пользователя из MAX.
- Внешние системы — CRM (сделки, выручка), платёжный шлюз (refund, MRR), биллинг подписок, поддержка (тикеты, время ответа), 1С/ERP.
Хорошая аналитика склеивает все три уровня по user_id (для бота это ID пользователя в MAX) и даёт цельную картину: пришёл из рекламы → нажал /start → дошёл до чекаута → оплатил → продлил подписку через месяц.
Базовый принцип: воронка, а не счётчики
Любой осмысленный анализ бота — это воронка. Пользователь куда-то заходит, что-то делает, до чего-то доходит. На каждом шаге часть отваливается. Задача — измерить каждый шаг и понять, где теряется больше всего.
Минимальная универсальная воронка:
- Открытие бота (
/startили клик по deeplink с UTM-меткой). - Активация — сделал первое значимое действие (выбрал услугу, согласился на ПДн).
- Целевое действие — оставил заявку, оплатил, записался.
- Удержание — вернулся через 7/30 дней.
Каждый шаг — отдельная метрика. Соотношения между ними — ключевые показатели.
Список ключевых метрик
Минимальный набор для большинства ботов в MAX:
| Метрика | Что показывает | Когда смотреть |
|---|---|---|
| DAU / WAU / MAU | Активная аудитория | Ежедневно |
| Retention D1 / D7 / D30 | Возвращаемость по когортам | Еженедельно |
| Conversion воронки | Узкие места | После каждого релиза |
| Session length | Глубина вовлечения | Еженедельно |
| Command frequency | Какие команды живые | Ежемесячно |
| CTR inline-кнопок | Качество UX | После A/B |
| NPS / CSAT | Удовлетворённость | Раз в квартал |
| ARPU / LTV | Деньги на пользователя | Ежемесячно |
| CAC по источникам | Эффективность каналов | Еженедельно |
| Доля заблокировавших бота | Здоровье рассылок | Ежедневно |
DAU/MAU — базовая метрика, но без воронок она бесполезна: можно гнать трафик и держать DAU, теряя при этом 95% пользователей на первом шаге.
События, а не сообщения
Главная техническая ошибка — мерить «сколько сообщений отправлено». Это не метрика, а лог. Метрика — это событие (event), которое описывает поведение пользователя:
bot_opened— открыл бот (с UTM из deeplink);consent_given— согласился на обработку ПДн;service_selected— выбрал услугу (с параметром);form_step_completed— прошёл шаг анкеты (с номером шага);lead_submitted— отправил заявку;payment_succeeded— оплатил (с суммой);support_escalated— попросил оператора.
Каждое событие — это запись в БД или отправка в аналитику с временем, user_id и параметрами. Дальше из этих событий собираются любые отчёты.
Схема таблицы событий
Минимальная схема в 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-ы из бизнес-логики запрещены — иначе схема расползётся за месяц, и никто не вспомнит, что значит step_3_done.
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, "bot_opened", {
utm,
lang: ctx.from.language_code,
});
await ctx.reply("Привет! Это бот MAX-студии.");
});
Где хранить и обрабатывать данные
Несколько рабочих вариантов:
- PostgreSQL. Своя таблица
events(user_id,event_name,ts,propertiesJSONB). Простой, гибкий, отчёты строятся SQL-запросами в Metabase/Superset. Подходит до десятков миллионов событий. - ClickHouse. Когда событий миллионы в сутки, и PostgreSQL начинает тормозить на агрегациях. Колоночная СУБД, секундные ответы на годовых данных.
- Yandex Metrika. Бесплатно, готовые отчёты, удобная воронка. Подходит для Mini App MAX — внутри WebView обычная веб-аналитика.
- Amplitude / Mixpanel. Продуктовая аналитика «из коробки» — воронки, когорты, ретеншн без SQL. Дорого, данные хранятся вне РФ (учитывайте 152-ФЗ).
- PostHog (self-hosted). Open-source альтернатива Amplitude. Можно развернуть в РФ, бесплатно для своих данных.
Для большинства проектов оптимум — PostgreSQL + Metabase + Yandex Metrika для Mini App. Этого хватает на месяцы и закрывает 80% задач без больших затрат.
Сравнение аналитических систем
| Инструмент | Сильные стороны | Минусы | Для кого |
|---|---|---|---|
| PostgreSQL + Metabase | Полный контроль, дёшево | Надо строить руками | Все, у кого есть бэкенд |
| ClickHouse + Superset | Миллионы событий в день | Сложнее эксплуатация | High-load |
| Amplitude | Готовые воронки, когорты | Дорого, данные вне РФ | Зрелый продукт |
| Mixpanel | Гибкие отчёты | Дорого, данные вне РФ | Продуктовые команды |
| PostHog (self-hosted) | Open-source, можно в РФ | Надо администрировать | Команды с DevOps |
| Yandex Metrika | Бесплатно, цели, вебвизор | Только для Mini App | Mini App с веб-частью |
| Yandex DataLens | Российский SaaS, есть free | Только дашборды | Команды в РФ |
| Power BI / Tableau | Корпоративный сегмент | Дорого | Enterprise |
ClickHouse подключают, когда событий становится больше 10–20 млн в сутки — для среднего бота это означает сотни тысяч активных пользователей.
UTM-метки в /start — единственный способ разделить источники
Любая ссылка на бот MAX — это deeplink с параметром start. Параметр идеально подходит для UTM-меток, но на полноценную CSV-строку места не хватает: payload ограничен длиной и алфавитом [A-Za-z0-9_-]. Поэтому используют либо короткие коды, либо base64url-кодирование JSON.
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://max.ru/your_bot?start=${encodeUtm({
source: "vk_ads",
medium: "cpc",
campaign: "brand_ru_jan",
})}`;
При первом контакте бот декодирует payload и сохраняет UTM в профиль пользователя (users.first_utm). Дальше любая метрика разворачивается по источникам — это позволяет считать CAC и ROMI по каждому каналу, а не «в среднем по больнице».
Без UTM весь маркетинг — это «верим, что работает», но проверить нельзя.
Воронки — главное оружие
Воронка показывает, какой процент пользователей проходит через цепочку шагов. Базовые воронки для бота в MAX:
- Активация:
bot_opened→consent_given→email_or_phone_provided→first_meaningful_action. - Покупка:
bot_opened→catalog_viewed→cart_item_added→checkout_started→payment_succeeded. - Подписка:
bot_opened→plan_selected→payment_succeeded→subscription_active→subscription_renewed. - Поддержка:
support_requested→bot_answered→solvedилиsupport_escalated.
Когда видна структура, понятно, где «обрыв». Если 40% пользователей открывают чекаут, но только 5% платят — проблема в чекауте, а не в трафике. Если 80% соглашаются на ПДн, но только 10% доходят до телефона — проблема в шаге сбора телефона.
Когорты и ретеншн
Когортный анализ показывает, что происходит с пользователями со временем. Стандартные когорты:
- по дате регистрации (пришли в одну неделю);
- по источнику (рекламный канал, реферал);
- по сегменту (новый, активный, спящий);
- по первому действию (купил сразу 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 = 'bot_opened'
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-тесты на пользователях бота
Все небанальные изменения проверяются 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, "welcome_copy_v3", 2) === 0 ? "A" : "B";
// фиксируем exposure — без него тест нечестный
void track(userId, "experiment_exposure", {
experiment: "welcome_copy_v3",
variant: v,
});
return v;
}
Без события experiment_exposure (фиксации показа) тест нечестный: посчитать конверсию можно только по тем, кто реально увидел вариант. Подробнее про методологию A/B в боте — в отдельной статье; здесь важно зафиксировать обязательный минимум: гипотеза, метрика успеха, заранее посчитанный размер выборки и exposure-событие.
Дашборды и BI
Куда складывать графики:
- Metabase — самый простой старт, SQL-редактор, бесплатная open-source-версия.
- Apache Superset — мощнее Metabase, но сложнее в эксплуатации.
- Grafana — хороша для технических метрик (latency, ошибки), хуже для продуктовых.
- Yandex DataLens — российский SaaS, бесплатный лимит, удобен для команд в РФ.
- Power BI / Tableau — корпоративный сегмент, дорого.
Базовый набор дашбордов: «Главная» (DAU/MAU, конверсия, выручка), «Воронки», «Когорты», «Источники», «Ошибки». Каждый дашборд должен помещаться на один экран — иначе им никто не пользуется.
Пример SQL-запроса для дашборда «Воронка активации за вчера»:
WITH funnel AS (
SELECT
COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'bot_opened') AS s1_opened,
COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'consent_given') AS s2_consent,
COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'phone_provided') AS s3_phone,
COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'lead_submitted') AS s4_lead
FROM events
WHERE ts >= CURRENT_DATE - INTERVAL '1 day'
AND ts < CURRENT_DATE
)
SELECT
s1_opened,
s2_consent, ROUND(100.0 * s2_consent / NULLIF(s1_opened, 0), 1) AS cr_consent,
s3_phone, ROUND(100.0 * s3_phone / NULLIF(s1_opened, 0), 1) AS cr_phone,
s4_lead, ROUND(100.0 * s4_lead / NULLIF(s1_opened, 0), 1) AS cr_lead
FROM funnel;
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% относительно недели → сломался платёжный шлюз.
- Доля ошибок отправки сообщений выросла → массовая блокировка после рассылки или проблема на стороне MAX Bot API.
- 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, кроме обезличенногоuser_idMAX. - Уведомление РКН об обработке ПДн (если собирается что-то кроме обезличенного).
- Уведомление о трансграничной передаче, если используется Amplitude/Mixpanel/GA4 (серверы вне РФ).
- Право на удаление: должен быть механизм
/forgetили ручка в админке, которая удаляет пользователя изeventsиusers. - Агрегирование: для большинства дашбордов хватит
count,avg,sumбез сырых ID.
Для Mini App в MAX дополнительно — баннер 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(), базовые события (bot_opened, ключевые шаги воронки,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.
Итого
Аналитика бота в MAX — это связка модели событий, воронок, когорт, ретеншна и UTM-разреза. Минимум — собственная таблица событий + Metabase + связка с CRM. Без аналитики невозможно понять, где рвётся воронка и какие каналы окупаются. Срок внедрения базового слоя — 1–3 недели, а отдача проявляется уже после первого месяца наблюдений. Главное — не тащить в трекинг всё подряд, а явно описать события, которые отвечают на вопросы продукта, и не считать «подписчиков» KPI.
Частые вопросы
Какие метрики бота в MAX действительно важны?
Бизнес-метрики: conversion rate (доля открывших бота, дошедших до целевого действия), cost per lead и cost per acquisition при платном трафике, average revenue per user и LTV, retention D7 и D30, drop-off rate по шагам воронки. Метрики качества продукта: time to first value, доля сессий с обращением к оператору, доля «застрявших» пользователей, CSAT через прямой опрос. Бесполезны: количество отправленных ботом сообщений, число подписчиков в моменте без когорт, среднее время ответа бота (если это не SLA-метрика).
Где хранить аналитику событий бота MAX?
Четыре рабочих варианта. Своя PostgreSQL — таблица events (user_id, event_name, timestamp, properties JSONB), отчёты через SQL, простая и гибкая схема для большинства проектов. Yandex Metrika — бесплатно, готовые отчёты, удобная воронка для Mini App MAX. ClickHouse плюс Grafana или Superset — если событий миллионы и нужны быстрые агрегаты. Amplitude/Mixpanel/PostHog — готовые продуктовые дашборды, но первые два дорогие и хранят данные вне РФ. Для большинства проектов оптимум — события в своей БД и дашборды в Metabase или Superset.
Как правильно измерять воронку в боте MAX?
Через события (events), а не «количество сообщений». Минимальная универсальная воронка из четырёх шагов: открытие бота (/start или клик по deeplink с UTM), активация (первое значимое действие — согласие на ПДн, выбор услуги), целевое действие (заявка, оплата, запись), удержание (возврат через 7 и 30 дней). Каждый шаг — отдельная метрика, соотношения между шагами — ключевые показатели. Drop-off rate показывает, где отваливается больше всего пользователей и куда направить оптимизацию.
Зачем нужны UTM-метки в ссылках на бот MAX?
UTM-метки (utm_source, utm_medium, utm_campaign) в deeplink на бот позволяют считать конверсию по каждому каналу трафика, сравнивать качество разных источников, перераспределять рекламный бюджет в пользу работающих. Параметр start в deeplink ограничен длиной и алфавитом, поэтому UTM кодируют либо короткими кодами (start=vk_jan), либо base64url JSON. При первом контакте бот декодирует payload и сохраняет UTM в профиль пользователя — дальше любая аналитика разрезается по источнику. Без UTM весь маркетинг превращается в «верим, что работает».
Как делать A/B-тесты в боте MAX?
Технически — функция-распределитель, которая по hash от user_id (sha256(experiment:user_id) % N) определяет вариант для каждого пользователя, и параметр «вариант эксперимента» добавляется в каждое событие этого пользователя. Обязательно фиксировать exposure — событие experiment_exposure с experiment_id и variant в момент показа: без него тест нечестный, считать конверсию можно только по тем, кто реально увидел вариант. Через 1–2 недели накапливаются данные, и группы сравниваются по целевой метрике. Минимальный размер выборки считается заранее по ожидаемому эффекту.
Что такое когортный анализ и зачем он боту?
Когорта — группа пользователей, пришедших в один период (например, неделю). Когортный анализ показывает, как себя ведёт каждая группа со временем — D1, D7, D14, D30, D60 retention. Стандартный отчёт: таблица, где по строкам — недели регистрации, по столбцам — дни с момента регистрации, в клетках — доля пользователей, которая ещё активна. Это показывает, действительно ли бот удерживает аудиторию, или просто «прокручивает» новый трафик. Простые цифры «всего пользователей» здесь бесполезны — средний ретеншн часто врёт.
Как соблюсти 152-ФЗ при сборе аналитики бота?
Минимальные требования. Согласие на обработку ПДн до сбора телефона, email и любых идентификаторов, кроме обезличенного user_id MAX. Уведомление РКН об обработке ПДн (если собирается что-то кроме обезличенного). Уведомление о трансграничной передаче, если используется Amplitude, Mixpanel или GA4 (серверы вне РФ). Право на удаление — механизм /forget или ручка в админке, удаляющая пользователя из events и users. Агрегирование данных в дашбордах через count/avg/sum без сырых ID. Для Mini App с Yandex Metrika — баннер cookies, потому что метрика ставит идентификатор в localStorage.