Вы передаёте click ID в Telegram-бота через ?start=, а ваш postback возвращается пустым для части Android-установок, которые все дружно считают реальным трафиком. Бот залогировал команду /start. Просто он так и не получил ваш payload — либо получил, но из пакета, который ваша отчётность никогда не учитывала. Оба объяснения дают одинаковый симптом: разрыв, который выглядит как баг трекинга, а на деле является пробелом в маршрутизации.
Три способа передать значение в Telegram
t.me-ссылки передают значение в Telegram через три разных параметра, и они не взаимозаменяемы:
| Параметр | Куда попадает значение | Лимит | Условие |
|---|---|---|---|
?start=<payload> | передаётся боту как аргумент команды /start | до 64 символов base64url | посетитель должен тапнуть START |
?startapp=<payload> | init data Mini App, только на клиенте | до 512 символов | доходит до вашего сервера, только если сам Mini App пересылает и валидирует значение |
?startchannel | простой флаг | вообще без payload | здесь нечего прикрепить |
?start=: создан именно для этой задачи
Это параметр, который ведёт себя так, как большинство трекеров ожидает от параметра deep link. Он передаётся боту напрямую как аргумент команды /start, то есть логика вашего бота читает его так же, как любой другой аргумент команды — не нужна отдельная обработка init data, не нужен собственный клиентский код. Ограничение, которое на практике действительно имеет значение, — лимит в 64 символа: это base64url, который комфортно вмещает большинство внутренних click ID, но достаточно тесен, чтобы дополненный или многословный идентификатор переполнил его раньше, чем вы заметите. И значение приходит только тогда, когда посетитель тапает START — t.me-ссылка, которая открывает чат, но так и не получает тап, не передаёт payload никуда, независимо от того, что вы положили в URL.
https://t.me/your_bot?start=dc_9Kx2Qz
доходит до бота как:
/start dc_9Kx2Qz
В этом цикле ничего не меняется в зависимости от платформы или того, какой пакет Telegram обработал тап — payload доходит до бота одинаково, маршрутизировал ли его Android через Play-сборку, APK с telegram.org или Telegram X, и независимо от того, пришёл ли он через переписывание tg://resolve на iOS. Единственная переменная, которая влияет на то, что видит бот, — сам payload.
?startapp=: совсем другое животное
?startapp= выглядит как увеличенная версия ?start= — больше символов разрешено, до 512, — и именно это сходство провоцирует ошибку. Ведёт он себя совсем иначе. Значение передаётся на клиенте, в init data Mini App, а не в обработчик команды бота. Само по себе это значение не доходит до вашего сервера. Оно доходит до сервера, только если код Mini App, которым управляет оператор, читает эти init data и пересылает их, с собственной валидацией, туда, где вы собираете данные. Если вы не контролируете Mini App или если Mini App не построен так, чтобы пересылать значение, payload может лежать прямо в клиенте и ни разу не коснуться бэкенда, к которому у вас есть доступ.
?startchannel: нести нечего
?startchannel — это простой флаг. Он вообще не принимает значение. Если план состоял в том, чтобы передать click ID в deep link канала так же, как ?start= передаёт его боту, здесь нет для этого механизма — параметр просто не построен для того, чтобы держать payload, и то, что вы к нему добавите, ничего не изменит.
Что происходит на iOS: переписывание tg://resolve
На iOS t.me-ссылке не нужно, чтобы вы вручную строили URL с собственной схемой. Подтверждено, что работают два переписывания:
https://t.me/<bot>?start=<payload> -> tg://resolve?domain=<bot>&start=<payload>
https://t.me/<channel> -> tg://resolve?domain=<channel>
Случай с ботом — тот, что имеет значение для трекинга: payload start выживает при переписывании целым, переносится как собственный параметр в URL tg://resolve, а не теряется и не перекодируется по дороге. Если ваша ссылка t.me/<bot>?start=<payload> работает на Android, тот же payload доходит до того же бота на iOS без какой-либо дополнительной обработки с вашей стороны. Случай с каналом переписывается так же, только без payload start — каналы не боты, так что нет ничего эквивалентного, что можно было бы нести.
Ловушка Android-пакетов: три ID, один спрятан на виду
Именно здесь большинство настроек тихо теряют трафик без единой ошибки, которая бы это показала. Telegram на Android — это не один пакет, а три:
| Источник | Пакет |
|---|---|
| Сборка Play Store | org.telegram.messenger |
| APK, который Telegram раздаёт со своего сайта | org.telegram.messenger.web |
| Telegram X | org.thunderdog.challegram |
Большинство настроек маршрутизации ориентируются только на org.telegram.messenger, потому что именно он считается «тем самым» Telegram в Play Store. Но APK, который Telegram раздаёт напрямую с telegram.org — вовсе не через Play, — несёт совершенно другой package ID. Прочитано прямо из манифеста этого APK: package=org.telegram.messenger.web, versionName 12.10.0. Любой, кто установил Telegram с собственного сайта Telegram, а не из Play Store, работает именно с этим пакетом — и любая маршрутизация по пакетам, которая называет только org.telegram.messenger, никогда его не увидит.
Это не погрешность округления в ваших Android-цифрах. Это посетитель, на устройстве которого действительно установлен Telegram, корректно, из источника, которым управляет сам Telegram, — и который невидим для цепочки, построенной на один package ID.
Telegram X, org.thunderdog.challegram, — третий отдельный клиент поверх этого. Это отдельное приложение из отдельного дистрибутива, и его стоит включать в ту же цепочку пакетов, если реальная доля вашей аудитории им действительно пользуется.
Почему это выглядит как баг трекинга, а не пробел в маршрутизации
Пропущенный пакет не порождает ни одной ошибки ни в одной точке цепочки. Посетитель тапает вашу ссылку, Android передаёт её тому пакету Telegram, который установлен, это приложение открывается, бот получает /start с payload, прикреплённым точно так, как задумано, и логика вашего бота срабатывает. Со стороны Telegram всё сработало. Пробел существует только на вашей стороне, в предположении, заложенном в логику маршрутизации или отчётности, которая изначально решила, из каких пакетов ожидать трафик.
Именно поэтому org.telegram.messenger.web так легко пропустить на месяцы: это не сломанная установка, не ошибка в настройке бота и не payload, который не дошёл. Это трафик, который никогда не считался отдельным источником, потому что никто не зафиксировал, что Telegram выпускает второй Android-пакет вне Play. Аргумент /start, лимит в 64 символа, переписывание на iOS — ни одна из этих особенностей не отличается для этого пакета по сравнению с Play-сборкой. Единственное, что отличается, — знает ли ваш собственный список пакетов, что этот источник вообще существует.
Что на самом деле доходит до вашего postback
Сложите три нити вместе, и картина будет последовательной: для трекинга на основе /start ваш идентификатор выживает при передаче аргумента боту, выживает при переписывании схемы на iOS целым, а дальше полностью зависит от того, какие именно Android-пакеты называет ваша маршрутизация. Пропустите org.telegram.messenger.web — и вы не просто не отслеживаете редкий крайний случай, вы не отслеживаете каждого посетителя, который доверился собственной странице загрузки Telegram больше, чем Play Store. Для Mini Apps ?startapp= вообще доходит до сервера, только если пересылку делает ваш собственный код Mini App; ничто внешнее по отношению к этому Mini App само по себе значения не увидит.
Трекеру, который генерирует t.me-ссылку для кампании, нужен список пакетов, включающий org.telegram.messenger.web наряду с Play-сборкой, не потому что это экзотический крайний случай, а потому что это то же самое приложение, выданное через другую дверь. Telegram-роутинг DarkCore по умолчанию несёт все три Android-пакета именно по этой причине: распределение трафика между ними — не шум, который можно усреднить, а реальная популяция установок, о которой цепочка с одним пакетом просто никогда не спрашивает.
Что проверить, прежде чем доверять цифрам
Большая часть этого незаметна, пока вы целенаправленно не начнёте искать, потому что ни одна из проблем не вызывает ошибку на стороне Telegram. Короткий чек-лист перед запуском кампании ловит почти всё:
- Убедитесь, что ваш click ID помещается в 64 символа base64url, прежде чем он попадёт в
?start=— всё, что длиннее, либо обрезается, либо отклоняется, в зависимости от того, где применён лимит, и ни один из этих исходов не хочется обнаружить уже в продакшне. - Не маршрутизируйте payload
?startapp=, считая, что он автоматически появится на сервере. Это произойдёт, только если контролируемый вами Mini App явно его пересылает. - Если цель — донести payload в deep link канала, не полагайтесь на
?startchannel— это флаг, а не контейнер, и любое значение, которое вы к нему добавите, никуда не попадёт. - Добавьте
org.telegram.messenger.webв любую Android-цепочку пакетов, построенную для Telegram. Пропуск этого пакета не уменьшает ваш трафик — он просто не даёт вам увидеть часть, которая всегда там была. - Если Telegram X имеет реальную долю среди ваших посетителей, включите
org.thunderdog.challegramв ту же цепочку, а не рассматривайте его как отдельный, второстепенный случай.