Перейти к основному содержимому

Свойства событий

Свойства (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.origintelegram (сгенерировано Telegram) · custom (ваше бизнес-событие)
$event.typemessage · interaction · payment · membership · business · poll · reaction · boost · platform
$source.channeltelegram_bot_mtproto · telegram_bot_api · telegram_webapp
$source.producerworker_mtproto · sdk_dotnet · sdk_js · api
$source.sdk_versionверсия SDK или воркера, если она известна

Раздел $meta: служебные пометки

ПолеЗначение
$meta.unbilled_duplicatetrue — событие записано, но не списано с квоты, потому что то же самое действие 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_typeNumber/Stringвсе
from_idNumberвсе
message_id, update_type, is_editedNumber/String/Booleanвсе
callback_data, callback_idStringвсе
directionStringвсе
content_typeStringвсе
entitiesStringвсе
reaction_emoji, reactions, reactions_removedStringвсе
reaction_change, reaction_count_itemsString/Numberвсе
old_status, new_statusStringвсе
is_outgoingBooleanтокен бота (MTProto)
media_type, has_media, viewsString/Boolean/Numberтокен бота (MTProto)
from_is_bot, from_usernameBoolean/StringSDK (Bot API)
message_type, text_len, is_replyString/Number/BooleanSDK (Bot API)
command, command_params, command_tokenStringсообщения с командой
entities_count, has_url_entity, has_mention_entityNumber/BooleanSDK (Bot API)
webapp_user_id, webapp_auth_date, webapp_chat_typeNumber/StringWebApp

$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 никогда не присылает боту его собственные отправленные сообщения — такого обновления просто не существует. Поэтому для бота на 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_linkurl адресом служит сам текст, отдельного поля нет).

Именно поэтому в диалоге видно жирный текст и кликабельные ссылки: интерфейс нарезает текст по этим смещениям и никогда не разбирает его как разметку. Поле служит для отображения — фильтровать по нему не нужно; для условий вида «в сообщении была ссылка» есть $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_changeadded · 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) не считается сменой реакции.
Telegram-поля от SDK

Часть полей раздела $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. Это не ошибка: событие сохранено. Но её появление означает, что вы передаёте персональные данные, которые тут же выбрасываются, и дешевле перестать их отправлять.

Что дальше?

  • События — структура события целиком и канонические имена
  • Типы данных — как выбираются и работают типы
  • Фильтры — фильтрация по системным и пользовательским свойствам