Свойства событий
Свойства (props) — это дополнительные поля события, которые описывают его контекст и позволяют детально фильтровать и сегментировать данные. В Metriox есть два вида свойств: системные и пользовательские.
Два вида свойств
| Вид | Кто заполняет | Префикс | Можно отправлять? |
|---|---|---|---|
| Системные | Платформа | $ | Нет (только чтение) |
| Пользовательские | Вы | без $ | Да |
Системные свойства
Системные свойства собираются платформой автоматически и доступны только для чтения — вы не можете их отправлять или переопределять. Их имена всегда начинаются со знака $ и сгруппированы по разделам:
| Раздел | Что описывает |
|---|---|
$tg | Контекст Telegram: чат, отправитель, сообщение, взаимодействие |
$event | Классификация события: origin (telegram / custom) и type |
$source | Источник сбора: channel, producer, sdk_version |
$lookup | Гео-данные по IP: страна, регион, город |
$web | Веб-контекст (для событий из браузера / WebApp) |
$sender | Отправитель, если он отличается от пользователя |
Все разделы едины для всех источников — событие из токена бота, из SDK и из WebApp приводятся к одной и той же структуре (см. Единый формат).
По системным свойствам можно фильтровать и строить сегменты так же, как по обычным. Например:
| Свойство | Что описывает |
|---|---|
$tg.chat_id | идентификатор чата |
$tg.message_id | идентификатор сообщения |
$tg.direction | кто отправил сообщение — inbound (пользователь) или outbound (бот) |
$tg.entities | форматирование текста — жирный, ссылки, код |
$tg.inline_keyboard | кнопки, предложенные сообщением бота |
$event.type | категория события (message, interaction, payment, …) |
$source.channel | канал сбора события |
Полный список доступных системных свойств вы всегда видите прямо в конструкторе фильтров — там отображаются только те поля, которые реально встречаются в ваших данных.
Раздел $event и $source
$event классифицирует событие, $source фиксирует, откуда оно пришло:
| Поле | Возможные значения |
|---|---|
$event.origin | telegram (сгенерировано Telegram) · custom (ваше бизнес-событие) |
$event.type | message · interaction · payment · membership · business · poll · reaction · boost · platform |
$source.channel | telegram_bot_mtproto · telegram_bot_api · telegram_webapp |
$source.producer | worker_mtproto · sdk_dotnet · sdk_js · api |
$source.sdk_version | версия SDK или воркера, если она известна |
Раздел $meta: служебные пометки
| Поле | Значение |
|---|---|
$meta.unbilled_duplicate | true — событие записано, но не списано с квоты, потому что то же самое действие Telegram уже прислал другой источник |
Свойство появляется только у второй копии и только при двойном подключении. Отсутствие свойства
означает, что событие учтено в квоте обычным образом — специального значения false не пишется.
Событие с этой пометкой ничем не отличается в отчётах: оно хранится целиком, попадает в воронки,
retention и разбивки, и у него сохраняется собственный $source.channel. Пометка касается только счёта.
Раздел $tg: поля Telegram
$tg — это плоский набор полей ($tg.chat_id, $tg.chat_type, …), одинаковый для всех источников. Значения тоже приведены к канону: chat_type всегда в нижнем регистре (private/group/supergroup/channel), update_type — в snake_case. Разные способы подключения физически «видят» разный набор полей, поэтому колонка «Источник» показывает, где поле доступно:
| Поле | Тип | Источник |
|---|---|---|
chat_id, chat_type | Number/String | все |
from_id | Number | все |
message_id, update_type, is_edited | Number/String/Boolean | все |
callback_data, callback_id | String | все |
direction | String | все |
content_type | String | все |
entities | String | все |
reaction_emoji, reactions, reactions_removed | String | все |
reaction_change, reaction_count_items | String/Number | все |
old_status, new_status | String | все |
is_outgoing | Boolean | токен бота (MTProto) |
media_type, has_media, views | String/Boolean/Number | токен бота (MTProto) |
from_is_bot, from_username | Boolean/String | SDK (Bot API) |
message_type, text_len, is_reply | String/Number/Boolean | SDK (Bot API) |
command, command_params, command_token | String | сообщения с командой |
entities_count, has_url_entity, has_mention_entity | Number/Boolean | SDK (Bot API) |
webapp_user_id, webapp_auth_date, webapp_chat_type | Number/String | WebApp |
$tg.direction — кто отправил сообщение
Одно поле с двумя значениями: inbound (пользователь → бот) и outbound (бот → пользователь). Платформа заполняет его сама на любом источнике, поэтому фильтр «только ответы бота» работает одинаково для токена бота, SDK и WebApp.
$tg.content_type — что было в сообщении
То же самое по смыслу, но для содержимого: text, photo, video, voice, audio, animation, sticker, document, location, venue, contact, poll, dice, game, invoice, story, paid_media, giveaway, checklist.
Подключения сообщают об этом по-разному — Bot API называет вид сообщения (message_type), MTProto называет вложение (media_type), — поэтому оба приводятся к одному значению при записи события. Группируйте и фильтруйте по content_type: message_type и media_type остаются как исходные наблюдения конкретного подключения и заполнены только у него.
Что стоит знать:
text— это явное значение, а не «поле пусто». Сообщение без вложения читается какtext.- Сообщение с превью ссылки — это тоже
text. MTProto считает превью вложением, Bot API — нет; чтобы один и тот же текст не попадал в две разные группы, канон здесь —text. paid_mediaпоказывается вместо фото или видео внутри: заглянуть внутрь может только MTProto, и различие сломало бы согласованность.- Кастомный эмодзи попадает в
sticker, живая геолокация — вlocation. Различить их может только MTProto, поэтому детализация осталась вmedia_type. - Поле заполняется только у сообщений. У нажатий кнопок, изменений участников и реакций его нет.
- Значение появилось не задним числом: у событий, записанных до этого обновления, есть только исходные поля.
В таблице выше остались «сырые» наблюдения, из которых оно выводится: is_outgoing (флаг MTProto) и from_is_bot (флаг Bot API). Они сохраняются как есть, но фильтровать и сегментировать удобнее по direction — не нужно знать, какой источник записал событие.
Bot API никогда не присылает боту его собственные отправленные сообщения — такого обновления просто не существует. Поэтому для бота на Bot API исходящих сообщений в данных не будет, пока их не сообщит сам бот. Наш C#-SDK умеет делать это автоматически: оберните клиент через WithMetrioxCapture(...), и каждое отправленное сообщение попадёт в аналитику без дополнительного кода (см. SDK).
Для MTProto-воркера это не нужно: он видит отправленные сообщения сам. Но он, наоборот, не получает личные переписки (DM) бота — их приносит только SDK.
$tg.entities — форматирование текста
Telegram не присылает «размеченный» текст. Он присылает обычный текст плюс список отрезков: тип, смещение и длина. $tg.entities хранит этот список компактной JSON-строкой:
[{ "type": "bold", "offset": 0, "length": 5 }, { "type": "text_link", "offset": 6, "length": 4, "url": "https://metriox.com" }]
type— тип отрезка в терминах Bot API:bold,italic,underline,strikethrough,spoiler,code,pre,text_link,mention,hashtag,bot_command,custom_emojiи другие;offset/length— смещение и длина в UTF-16 (та же единица, что и в Telegram, и та же, в которой JavaScript индексирует строки). Это модель самого Telegram: он присылает не размеченный текст, а текст плюс отрезки — поэтому смещения, а не markdown;url— адрес ссылки, только уtext_link(уurlадресом служит сам текст, отдельного поля нет).
Именно поэтому в диалоге видно жирный текст и кликабельные ссылки: интерфейс нарезает текст по этим смещениям и никогда не разбирает его как разметку. Поле служит для отображения — фильтровать по нему не нужно; для условий вида «в сообщении была ссылка» есть $tg.has_url_entity.
До 26.07.2026 ключи были однобуквенными (t/o/l/u), и в событиях, записанных раньше, вы всё ещё увидите именно их. Платформа читает оба варианта, так что ничего делать не нужно: старые диалоги отображаются как раньше, а новые события пишутся длинными именами. То же касается $tg.inline_keyboard и $tg.reactions.
$tg.inline_keyboard — кнопки под сообщением
Клавиатура, которую бот приложил к исходящему сообщению, компактной JSON-строкой:
[{ "text": "💰 Купить", "callback_data": "buy" }, { "text": "Документация", "url": "https://metriox.com" }]
text— подпись кнопки, как её видит пользователь (есть всегда);callback_data— payload callback-кнопки. По нему платформа находит подпись нажатой кнопки, поэтому в диалоге вместоbuyвидно «💰 Купить»;url— адрес кнопки-ссылки.
Остальные типы кнопок (switch-inline, web-app, оплата, игра, запрос контакта…) не несут ни payload, ни адреса, поэтому в список не попадают. Ряды клавиатуры разворачиваются в один плоский список по порядку.
Поле заполняет либо MTProto-воркер (он видит отправки бота сам), либо SDK — для ботов на Bot API отправки бота может сообщить только сам бот, см. SDK. Задним числом клавиатуры не появляются: у сообщений, отправленных до подключения, её нет.
Реакции — какая именно и что с ней произошло
События реакций собирают и MTProto-воркер, и SDK, одинаковым набором полей.
| Поле | Что означает |
|---|---|
$tg.reaction_emoji | одна реакция «плоской» строкой — по этому полю и стоит группировать |
$tg.reactions | полный список компактной JSON-строкой |
$tg.reactions_removed | реакции, которые пользователь снял |
$tg.reaction_change | added · removed · changed · updated |
$tg.reaction_count_items | сколько разных реакций сейчас на сообщении |
$tg.reaction_emoji — это либо сам эмодзи (👍), либо custom:{id} для кастомного эмодзи, либо paid для платной реакции. Поле заполнено всегда, когда реакция известна: у кастомного эмодзи нет юникодного символа, и если оставлять поле пустым, такие реакции молча выпадали бы из любой группировки. Префикс custom: не может совпасть с настоящим эмодзи.
$tg.reactions хранит список так же, как $tg.entities — компактной JSON-строкой:
[{ "type": "emoji", "emoji": "👍", "total_count": 3 }, { "type": "custom_emoji", "custom_emoji_id": "5361675167" }]
type— вид реакции:emoji,custom_emoji,paid;emoji— эмодзи, только уemoji;custom_emoji_id— идентификатор документа, только уcustom_emoji;total_count— сколько пользователей поставили эту реакцию. Есть только в сводке по сообщению; у события «конкретный пользователь изменил реакцию» счётчика нет.
$tg.reaction_change вычисляется из состояния «до» и «после»:
added— реакции не было, стала;removed— была, сняли (что именно сняли — в$tg.reactions_removed);changed— заменили одну реакцию другой;updated— сводка по сообщению: предыдущего состояния в таком обновлении нет, поэтому направление не определить. Изменение только счётчиков (total_count) не считается сменой реакции.
Часть полей раздела $tg может дополнительно передавать Telegram-SDK — например $tg.inline_keyboard (клавиатура, которую бот приложил к исходящему сообщению; см. SDK). SDK отправляет такие поля с «голым» префиксом tg. (без $), а платформа сама переносит их в раздел $tg. Правило «имя нельзя начинать с $» при этом не нарушается: $-имена остаются только для чтения, а tg. — это зарезервированный префикс Telegram-полей.
Пользовательские свойства
Пользовательские свойства — это ваши произвольные поля, которые вы отправляете вместе с событием. Именно они делают аналитику осмысленной для вашего продукта.
{
"button_name": "buy_subscription",
"plan": "premium",
"price": 299,
"currency": "RUB",
"is_trial": false
}
Правила именования
- Только латинские буквы, цифры, точка
.и подчёркивание_(напримерscreen_name,payment.method,step1). - Имя должно начинаться с латинской буквы или
_и не может начинаться с$— этот префикс зарезервирован за платформой. - Максимальная длина имени — 128 символов.
- Значение относится к одному из четырёх типов данных: String, Number, DateTime, Boolean.
$?Префикс $ зарезервирован за системными свойствами платформы. Это гарантирует, что ваши поля никогда не конфликтуют с автоматически собираемыми и что структура данных остаётся предсказуемой. Такой подход используют и другие аналитические платформы (например, PostHog и Mixpanel).
Рекомендации
- Используйте
snake_case:user_id,is_premium,payment_amount. - Делайте имена описательными:
subscription_planвместоplan. - Держите тип одного и того же свойства одинаковым во всех событиях (см. Типы данных).
- Не отправляйте персональные данные в свойствах.
Прежняя формулировка этой рекомендации звучала как «не отправляйте в открытом виде», что читалось как совет хешировать. Дело не в этом: в отношении данных конечных пользователей ваших ботов оператор — вы, а Metriox обрабатывает их по вашему поручению. Всё, что вы положите в свойства, вы собираете под свою ответственность.
Свойства не проходят проверку значений: платформа контролирует имена ключей, типы и лимиты, но не смотрит, что внутри. Телефон, положенный в phone, будет сохранён.
Текст сообщений — отдельный случай: он не является свойством и хранится отдельно, а его сохранение можно отключить. См. Захват текста сообщений ниже.
Захват текста сообщений
Текст, который пользователь напечатал боту, хранится в отдельном поле события, а не в свойствах. По умолчанию он сохраняется, и это можно отключить для каждого бота отдельно.
Причина в том, что заранее неизвестно, что там окажется: номер телефона, адрес, сведения о здоровье. Часть таких сведений относится к специальным категориям персональных данных, для которых требуется отдельное согласие субъекта — и получить его может только владелец бота, не платформа.
Отключается в настройках бота, переключателем Store message text. Пока он включён, вы поручаете нам хранить текст и подтверждаете, что у вас есть для этого правовое основание, включая согласия там, где они требуются. Отключение действует на будущее: текст новых событий не сохраняется, ранее сохранённый удаляется по истечении срока хранения событий.
Что работает независимо от настройки:
- имена событий и их количество;
- команды:
$tg.commandи$tg.command_tokenразбираются из текста до того, как он отбрасывается; - все пользовательские свойства, которые вы отправляете сами.
Что пропадает, если настройку выключить:
- поиск по тексту сообщений и просмотр переписки в интерфейсе;
$tg.command_params— параметры команды, то есть то, что пользователь написал после её имени.
Если настройку выключили, а SDK или HTTP API всё равно прислали текст, событие принимается, текст отбрасывается, а в ответе возвращается диагностика message_text_not_captured. Это не ошибка: событие сохранено. Но её появление означает, что вы передаёте персональные данные, которые тут же выбрасываются, и дешевле перестать их отправлять.
Что дальше?
- События — структура события целиком и канонические имена
- Типы данных — как выбираются и работают типы
- Фильтры — фильтрация по системным и пользовательским свойствам