Я несколько лет смотрю на одну и ту же колонку в Telegram Ads.
Views.
Рядом CPM, бюджет, подписки или старты бота. Логика кажется очевидной: заплатили за тысячу показов — получили тысячу показов.
Но недавно я поймал себя на довольно странной мысли.
А что технически произошло, когда в кабинете прибавился ещё один View?
Telegram отправил рекламу на устройство?
Она появилась где-то на экране?
Пользователь увидел весь блок?
Прочитал текст?
Кнопка попала в поле зрения?
Реклама должна была пробыть на экране какое-то время?
И самое неприятное: то клиентское событие, которое Telegram называет просмотром, — это точно то же самое событие, за которое рекламодатель платит?
Я пошёл в документацию MTProto, актуальную TL-схему, TDLib и исходники Telegram Desktop.
Первый ответ нашёлся довольно быстро.
Второй оказался интереснее.
А на третьем Telegram просто заканчивается и начинается серверный чёрный ящик.
Сервер прислал рекламу. View ещё не случился
Я почему-то ожидал, что механика будет примерно такой:
Telegram выбрал объявление ↓ отправил его пользователю ↓ +1 View
На уровне протокола всё устроено аккуратнее.
Для обычного канала клиент получает рекламный контент через отдельный метод:
messages.getSponsoredMessages(peer)
Сервер возвращает messages.SponsoredMessages, внутри которого находятся один или несколько объектов SponsoredMessage.
Но сам факт получения этого объекта не считается просмотром.
Для просмотра существует другой RPC:
messages.viewSponsoredMessage(random_id)
То есть в самой архитектуре Telegram доставка объявления и регистрация просмотра разведены на два события.
Если очень грубо, путь выглядит так:
Telegram Server ↓ getSponsoredMessages() ↓ SponsoredMessage ↓ Telegram Client ↓ рендер объявления ↓ выполнено условие видимости ↓ viewSponsoredMessage(random_id) ↓ Telegram Server
Это первая полезная вещь.
View в Telegram — не served impression.
Сервер может вернуть рекламный объект клиенту, но пока тот не дошёл до нужного состояния на экране, отдельного view-события не возникает.
Тут мне стало интересно: а что за «нужное состояние»?
Чтобы получить View, весь текст должен оказаться на экране. Кнопка — необязательно

Ответ нашёлся прямо в документации Sponsored Messages.
Для стандартного рекламного сообщения Telegram предлагает вызывать viewSponsoredMessage, когда весь текст объявления находится на экране.
Причём для рекламы в канале CTA-кнопка из этого условия прямо исключена.
То есть Telegram не требует, чтобы пользователь увидел рекламную карточку целиком до последнего пикселя. Достаточно, чтобы полностью дошёл до видимости текст. Кнопка может оставаться ниже viewport.
Я полез проверять это в Telegram Desktop.
Там sponsored-элементы при обработке видимой части истории проходят через markSponsoredViewed(...). Этот метод проверяет положение элемента относительно видимой области, а затем менеджер sponsored messages вызывает свой view().
Что особенно важно: порог в Desktop действительно расположен около нижней границы рекламного контента до области CTA-кнопки.
То есть документация и клиент здесь совпадают.
И ещё одна деталь.
Я не нашёл в этом пути таймера.
Нет:
показываем 1 секунду ↓ засчитываем View
или:
50% объявления видно 2 секунды ↓ засчитываем View
Для обычного sponsored message Telegram публично такого критерия не описывает, а в соответствующем Desktop-path между достижением порога видимости и отправкой события dwell timer нет.
Это не значит, что сервер потом вообще ничего не фильтрует. Этого мы как раз не знаем.
Но на стороне клиента подтверждаемая механика такая:
текст дошёл до необходимой видимости → клиент может отправить View.
Не чтение.
Не внимание.
Не секунда просмотра.
И даже не обязательная видимость CTA.
Вот после этого слово Views в кабинете я уже читаю немного иначе.
Что именно отправляется Telegram
Текущий метод на Layer 223 довольно минималистичный:
messages.viewSponsoredMessage( random_id ) = Bool
В старых слоях метод содержал больше контекста. Начиная с Layer 202 peer из sponsored view/click/report methods убрали.
Сейчас серверу достаточно random_id рекламного объекта.
И это интересная архитектурная деталь.
random_id — не campaign ID рекламодателя и не ID канала. Это opaque identifier конкретного sponsored object, по которому сервер сам восстанавливает нужный контекст.
Получается примерно так:
SponsoredMessage ├── random_id ├── url ├── title ├── message ├── media ├── button_text └── ... ↓ viewSponsoredMessage(random_id)
С клиентской стороны всё довольно прозрачно.
Но вот здесь я впервые упёрся в стену.
viewSponsoredMessage() ещё не означает «у рекламодателя списались деньги»
Очень хотелось написать красивую схему:
viewSponsoredMessage() ↓ +1 View ↓ CPM / 1000 списались
Проблема в том, что доказать её по публичным данным нельзя.
Telegram действительно продаёт рекламу по CPM.
В Terms Impression определяется как случай показа рекламы пользователю, зафиксированный в Statistics. В кабинете рекламодатель действительно видит Views/Impressions и расход бюджета.
Но публичного контракта:
1 успешный viewSponsoredMessage RPC = 1 принятый рекламный View = 1 billable impression
я не нашёл.
И это важно не как юридическая оговорка, а как архитектурная граница.
Между клиентским RPC и рекламным кабинетом остаётся сервер Telegram:
viewSponsoredMessage(random_id) ↓ ┌─────────────────────────────┐ │ Telegram backend │ │ │ │ deduplication ? │ │ invalid traffic ? │ │ anti-fraud ? │ │ accounting ? │ │ campaign state ? │ │ frequency rules ? │ └─────────────────────────────┘ ↓ Advertiser Statistics ↓ Billing
Что именно происходит внутри этого прямоугольника — Telegram публично не раскрывает.
Поэтому самое точное определение, которое я сейчас могу дать:
мы умеем достаточно хорошо восстановить момент, когда официальный клиент регистрирует sponsored View. Но не умеем доказать, что каждый такой сырой event один к одному превращается в оплаченный Impression.
Мне эта граница нравится.
Потому что reverse engineering заканчивается именно там, где начинаются деньги.

А если открыть канал ещё раз?
Следующая мысль была очевидной.
Если я открыл канал, увидел рекламу, закрыл его и сразу открыл снова — Telegram опять идёт на сервер за новым объявлением?
Нет. По крайней мере не обязательно.
В документации sponsored messages есть пятиминутное кеширование ответа.
В Telegram Desktop это реализовано буквально через:
kRequestTimeLimit = 5 * 60 * 1000
Если предыдущий sponsored response достаточно свежий, новый сетевой запрос не отправляется.
TDLib независимо делает практически то же самое: кеш для sponsored messages живёт около 300 секунд.
То есть:
открыли канал ↓ получили sponsored response ↓ закрыли ↓ открыли снова через минуту ↓ клиент может использовать кеш
Новый заход пользователя не обязан означать новый поход рекламной системы к серверу и новый выбор объявления.
Но здесь опять важно не перескочить через один логический этаж.
В Desktop есть ещё локальная защита от повторной отправки view для того же random_id в пределах примерно тех же пяти минут. В TDLib cached sponsored object хранит is_viewed_ и второй раз свой view не отправляет.
Звучит очень похоже на frequency cap.
Но это не frequency cap рекламной кампании.
Это client-side suppression.
Мы не знаем:
вернёт ли сервер тот же creative после обновления кеша;
будет ли у него тот же
random_id;есть ли server-side лимит на пользователя;
действует ли он на объявление, кампанию или рекламодателя;
существует ли вообще публичное правило вида «не чаще N раз в сутки».
Публично этого нет.
Поэтому фраза:
«Telegram показывает одно объявление пользователю не чаще раза в пять минут»
из найденного кода не следует.
Следует только:
официальный клиент не спамит одним и тем же view RPC для одного cached sponsored object.
Разница небольшая на слух и огромная для медиапланирования.
Миллион Views — это миллион человек?
После всего выше ответ уже довольно очевидный.
Нет оснований так считать.
Telegram в рекламных Terms описывает Impression как occurrence показа — событие экспозиции, а не уникального человека. В публичной документации нет равенства:
1 View = 1 Unique User
и нет публичной campaign-frequency модели, из которой такое равенство можно было бы вывести.
Поэтому если в отчёте написано:
1 000 000 Views
я бы не превращал это в:
охватили миллион человек.
Корректнее:
система зафиксировала миллион рекламных impressions/views по своей методике.
Сколько среди них уникальных пользователей — другой вопрос.
И вот здесь предыдущая тема про пересечения аудиторий внезапно встречается с этой.
Даже если я идеально знаю подписчиков каналов, это всё равно не даёт мне точный рекламный unique reach: между membership и impression находится ещё eligibility, Premium, аукцион, клиентская выдача и frequency, часть которой Telegram не раскрывает.
С кликами ещё веселее
Telegram декларирует очень приватную рекламную модель и отдельно говорит, что не строит рекламный таргетинг на основе персонального поведения пользователя.
На этом фоне я ожидал, что внутри протокола вообще не будет классического ad-click event.
Он есть.
И довольно подробный:
messages.clickSponsoredMessage( media?, fullscreen?, random_id )
View и click — два разных RPC.
Но слово click здесь тоже немного обманчиво.
Например, нажатие на рекламное media может отправить:
media = true
Если пользователь открыл видео в fullscreen — используется комбинация media и fullscreen.
То есть clickSponsoredMessage — скорее семейство interaction events, чем строго «пользователь перешёл на сайт рекламодателя».
Это важный нюанс.
Если когда-нибудь поверх этих событий показывается одна агрегированная метрика «Clicks», нельзя автоматически считать её эквивалентом outbound click без определения того, какие interaction types туда вошли.
И здесь появляется забавный privacy-парадокс.
Telegram говорит, что не профилирует пользователя по тому, нажимал ли он рекламу.
А MTProto при этом буквально отправляет серверу событие рекламного взаимодействия.
Я не вижу здесь обязательного противоречия.
Есть две разные вещи:
Telegram получил interaction event и Telegram хранит историю рекламных кликов пользователя и использует её для поведенческого таргетинга
Первое подтверждается протоколом.
Второе Telegram отрицает.
А вот сколько живёт raw-event, как он агрегируется и когда теряет связь с пользовательской сессией — уже снова server-side и публично неизвестно.
В какой-то момент я понял, что исследую уже не один рекламный блок

У меня в голове до сих пор сидела старая картинка Telegram Ads:
рекламное сообщение где-то внизу канала.
В актуальном протоколе это уже неполное описание системы.
Канал
Классическая механика никуда не исчезла: sponsored message может находиться после обычного контента.
Но в текущем messages.sponsoredMessages есть posts_between.
Если параметр приходит, Telegram Desktop переключается с AppendToEnd на InjectToMiddle и умеет вставлять sponsored messages между обычными постами канала.
То есть утверждение:
реклама Telegram всегда находится только под последним постом
уже технически устарело.
Бот
Боты используют тот же общий sponsored-message механизм:
getSponsoredMessages(bot_peer)
Но Desktop отображает такую рекламу совершенно иначе — в отдельном action-bar-подобном блоке над чатом.
View-event при этом живёт в той же sponsored infrastructure.
Для меня отсюда следует полезная вещь.
Если channel ad и bot ad в итоге порождают один тип view event, это ещё не значит, что они покупают одинаковое количество человеческого внимания.
Протокол одинаковый.
UX разный.
Следовательно, сравнивать их только по CPM — уже аналитическое допущение.
Видео
Здесь вообще отдельный сценарий.
Начиная с Layer 205 getSponsoredMessages получил опциональный msg_id для рекламы во время полноэкранного просмотра видео в канале.
Сервер может вернуть:
start_delay between_delay min_display_duration max_display_duration
Получается уже почти рекламный scheduler внутри видеоплеера.
Например:
video starts ↓ start_delay ↓ sponsored overlay ↓ min_display_duration ↓ пользователь уже может скрыть ↓ max_display_duration ↓ overlay скрывается автоматически
Но здесь есть очень опасная ловушка.
min_display_duration — это время до возможности скрыть рекламу.
В документации не написано, что именно столько секунд требуется для биллингового View.
То есть нельзя взять это поле и написать:
«Video View в Telegram считается после N секунд».
Мы этого не знаем.
Поиск
У sponsored search вообще отдельный retrieval method:
contacts.getSponsoredPeers(query)
Рекламный результат появляется внутри глобального поиска.
Но когда такой sponsored peer доходит до видимой области, Telegram снова использует знакомый:
viewSponsoredMessage(random_id)
и тот же clickSponsoredMessage для взаимодействия.
Мне нравится эта архитектура.
Разные места получения рекламы.
Разные интерфейсы.
Но общий язык событий:
random_id → View → Interaction.
При этом наличие API ещё не означает, что каждый формат одинаково доступен каждому рекламодателю в каждом типе кабинета. В частности, для Search протокол и рекламные policies существуют, но универсальная доступность во всех native accounts публично не описана.
Это ещё одна вещь, которую постоянно путают в Telegram Ads:
протокол умеет ≠ рекламный кабинет прямо сейчас даёт купить.
И вот здесь старый CPM начинает немного разваливаться
Не математически.
С точки зрения смысла единицы измерения.
Представим:
CPM канала = 2
CPM бота = 2
В обоих случаях куплено тысяча Views.
Но один View получился внутри истории канала после достижения full-text visibility condition.
Другой — в action-bar-style размещении над ботом.
А видео живёт ещё в третьем контексте с отдельными server-driven таймерами.
Протокол способен свести всё это к рекламным events.
Человеческое внимание не обязано быть одинаковым.
Поэтому я бы очень осторожно переносил benchmark:
«У нас нормальная конверсия с тысячи Views такая-то»
между разными surfaces.
Одинаковый знаменатель ещё не означает одинаковую экспозицию.
Telegram View — не Google Active View. И не надо делать вид, что все «показы» одинаковые
Здесь я специально перепроверил определения, потому что в первом исследовании была слишком красивая, но неверная версия про «Telegram считает один пиксель, Meta и Google — 50% на секунду».
Всё сложнее.
Обычный Telegram sponsored View использует собственный критерий:
полностью видимый текст объявления; CTA у channel ad не обязателен; dwell time публично не задан.
У Meta обычный impression тоже не равен MRC-style «50% на секунду»: Meta отдельно различает impressions и reach, и один аккаунт может дать несколько impressions.
У Google метрика Active View для viewable display действительно использует критерий как минимум 50% площади в течение одной непрерывной секунды; для видео — 50% площади не менее двух секунд.
То есть слово impression между платформами плохо работает как физическая единица вроде килограмма.
У каждого продукта своя measurement semantics.
Для закупщика отсюда следует довольно простой вывод:
CPM Telegram и CPM другой рекламной системы нельзя сравнивать как цену одного и того же стандартизированного контакта только потому, что обе метрики называются CPM.
Перед сравнением надо хотя бы понять, что именно находится в знаменателе.
Что я теперь вижу, когда открываю статистику кампании
Сам Telegram публично документирует Views и показывает, сколько пользователей после sponsored-message View присоединились к рекламируемому каналу или запустили бота.
Но формулировка «после просмотра» оставляет очень много вопросов.
Публично я не нашёл ответа:
каково attribution window;
нужен ли click перед join;
это view-through или click-through attribution;
какому View засчитается join после нескольких экспозиций;
используется last touch или другая схема;
как обрабатываются повторные starts/joins.
Поэтому показатель:
3% Join Rate
остаётся полезной продуктовой метрикой Telegram.
Но я бы не пытался по ней восстановить неизвестную attribution model.
То же самое с click telemetry.
Протокол её явно имеет.
Публичное описание native Statistics при этом делает акцент на Views и joins/starts, а не описывает всю interaction-схему MTProto.
Получается интересная конструкция:
ПРОТОКОЛ ЗНАЕТ ------------------- view click media interaction fullscreen interaction report/hide РЕКЛАМОДАТЕЛЬ В ПУБЛИЧНО ОПИСАННОЙ СТАТИСТИКЕ ------------------- видит только часть агрегированной картины
И это нормально.
Рекламный интерфейс вообще не обязан быть дампом внутренней telemetry.
Просто об этом полезно помнить, когда по четырём колонкам кабинета пытаются сделать вывод о всей системе.
Что мы реально вскрыли, а что осталось внутри Telegram
После нескольких дней чтения документации граница получилась очень чёткая.
Снаружи мы можем довольно хорошо восстановить:
SponsoredMessage schema ↓ получение рекламы ↓ 5-minute cache ↓ размещение в UI ↓ условие видимости ↓ viewSponsoredMessage ↓ clickSponsoredMessage ↓ client-side duplicate suppression
А дальше:
════════════ TELEGRAM SERVER ════════════ auction ranking server frequency fraud filtering event deduplication accepted advertiser impression join/start attribution billing inventory forecasting ═════════════════════════════════════════
Публичных ответов здесь уже нет.
И это, пожалуй, главный результат всего исследования.
Не то, что я нашёл секретный алгоритм счётчика Telegram.
Наоборот.
Теперь понятно, где именно заканчивается то, что можно доказать.
Если свести всё к одному рекламному View
Для стандартной рекламы в канале я бы сейчас описал путь так:
1. Пользователь открывает канал. 2. Telegram Client использует свежий sponsored cache или запрашивает: getSponsoredMessages(peer) 3. Сервер возвращает SponsoredMessage. 4. Клиент размещает и отрисовывает рекламу. 5. Весь текст объявления оказывается на экране. CTA-кнопка для channel ad может быть ещё не видна. 6. Клиент отправляет: viewSponsoredMessage(random_id) 7. Telegram Server принимает событие. 8. Дальнейшая фильтрация и accounting происходят внутри закрытой серверной системы. 9. В рекламной Statistics появляется агрегированный View, если событие прошло внутренние правила Telegram. 10. Если пользователь взаимодействует с рекламой, отдельно отправляется clickSponsoredMessage(...).
Первые шесть шагов можно восстановить довольно уверенно.
В восьмом шаге мы упираемся в сервер.
А формулировка девятого шага специально осторожная: мы не можем публично доказать one-to-one mapping между каждым RPC и каждым billable View.
И вот с такой картиной я уже гораздо спокойнее смотрю на CPM.
Telegram действительно продаёт Views.
Просто View — это не «человек прочитал рекламу».
Не «уникальный человек».
Не «сервер отправил объявление».
Не обязательно стандартизированный viewable impression из другой рекламной системы.
Это событие внутри собственной measurement architecture Telegram.
И если я покупаю его миллионами, мне всё-таки полезно понимать, что именно я покупаю.
Когда я начинал копать эту тему, хотел найти одно условие в коде и закрыть вопрос.
В итоге нашёл рекламную подсистему с собственным fetch/cache/view/click flow, разными placements, video scheduler и несколькими довольно большими серверными белыми пятнами.
Самое интересное теперь как раз находится за этими пятнами.
Мне хочется отдельно разобрать frequency и saturation: можно ли по публичным данным хоть как-то восстановить, сколько раз один пользователь способен встретить одну кампанию.
Вторая тема — атрибуция Join/Bot Start: что вообще можно экспериментально проверить про окно и механику зачёта конверсии, если Telegram её не документирует.
И третья — аукцион. Не «какую ставку поставить», а что из открытого API, поведения кабинета и controlled tests можно узнать о выборе рекламного inventory.
Если среди читателей есть люди, которые копали Telegram Desktop, TDLib или сами строили MTProto-клиенты — особенно интересно, где я здесь ошибся или что пропустил.
Давайте попробуем добить эту схему коллективно.
А если разбор зайдёт, следующим материалом полезу уже не в маркетинговую документацию Telegram Ads, а туда, где начинаются самые неприятные вопросы: frequency, attribution и server-side accounting.

