Посетитель тапает вашу ссылку, нацеленную на Instagram или Telegram, и приложение не открывается. Вместо него открывается Chrome, прямо на странице, которую вы и строили редирект, чтобы обойти. Клик засчитался. В дашборде — обычный клик. Результат — вкладка браузера.
Это не один режим сбоя с одним фиксом. Это цепочка независимых решений браузера, Android и приложения-цели, и провал в любом одном звене этой цепочки даёт одинаковый симптом: браузер вместо приложения. Разобраться в цепочке — единственный способ понять, какое именно звено подвело.
Обычная https-ссылка не знает, что должна открыть приложение
https://-URL — это просто ссылка. То, что её рендерит, само решает, что с ней делать, и ничего в самой схеме https не несёт инструкции передать клик нативному приложению. Эту инструкцию нужно добавить намеренно. Ссылка https:// сама по себе каждый раз рендерится как веб-страница, независимо от того, установлено приложение-цель или нет.
intent:// — это та самая недостающая инструкция
Форма, которая её добавляет:
intent://host/path#Intent;scheme=https;package=<package>;end
Она называет пакет, которому Android должен передать клик, сохраняя при этом базовую схему https в intent для проверки. Если названный пакет установлен и зарегистрирован на этот контент, Android передаёт клик напрямую, без остановки в браузере.
Место, где людей чаще всего ловит ловушка, — fallback. intent://-ссылка может нести S.browser_fallback_url на случай, если пакет-цель отсутствует, но это поле принимает только значения http и https. Укажите вместо этого market://-URL — и Android просто отбросит его. Fallback, который вы вроде бы настроили, никогда не сработает так, как обещает синтаксис.
Одно приложение редко бывает одним пакетом
Логика fallback усложняется в тот момент, когда вы пробуете больше одного пакета, а для нескольких крупных приложений приходится именно так. Одно и то же приложение поставляется под разными именами пакетов в зависимости от того, откуда пришла установка:
| Приложение | Пакеты |
|---|---|
| Telegram | org.telegram.messenger (Play Store), org.telegram.messenger.web (APK с telegram.org), org.thunderdog.challegram (Telegram X) |
com.whatsapp, com.whatsapp.w4b (Business) | |
| TikTok | com.zhiliaoapp.musically, com.ss.android.ugc.trill |
com.instagram.android | |
| Shopee | отдельный пакет для каждой страны: com.shopee.id, .br, .vn, .th, .ph, .tw, .my, .sg, .mx, .cl, .ar |
| Amazon | com.amazon.mShop.android.shopping глобально, кроме Индии: in.amazon.mShop.android.shopping |
Раскол Telegram застаёт врасплох больше всего людей. APK, который Telegram раздаёт со своего собственного сайта, имеет другой package id, чем сборка из Play Store, а именно org.telegram.messenger.web, подтверждено прочтением прямо из манифеста этого APK. Нацельте цепочку intent только на пакет Play Store, и каждый посетитель, установивший Telegram напрямую с telegram.org, становится невидимым для вашего routing, неотличимым от приложения, которого вообще нет.
Amazon — напоминание проверять, а не предполагать: японский магазин Amazon использует тот же глобальный пакет, что и везде. Пакет jp.amazon.mShop.android встречается на сайтах-зеркалах APK, но это не действующий листинг в Play. Построение цепочки вокруг него нацелено в ничто реальное.
Это не опциональная деталь для routing, нацеленного только на одну сборку. Она становится важной в тот момент, когда вы пытаетесь покрыть всю базу установок приложения, потому что пакеты выше — не взаимозаменяемые кандидаты, конкурирующие за одного и того же посетителя. Посетитель со сборкой с telegram.org и посетитель со сборкой из Play — два разных человека с точки зрения Android, и каждому нужен собственный названный пакет в цепочке, прежде чем хоть один из них доберётся до приложения.
Правило про fallback, которое почти все понимают наоборот
Как только вы выстраиваете цепочку из нескольких пакетов (сборка Play, потом региональная сборка, потом последний вариант), инстинкт подсказывает прицепить fallback URL и к первой попытке тоже, на всякий случай.
Это ровно наоборот. Если первый кандидат несёт fallback, а его пакет не установлен, посетителя выталкивает в браузер ещё до того, как попробуют второго кандидата. Цепочка так и не доходит до пакета, который бы сработал. Ни один шаг в цепочке с несколькими пакетами не должен нести fallback URL, включая последний. Fallback принадлежит после того, как исчерпаны все кандидаты, и применяется уже вашей собственной посадочной страницей, когда вы точно знаете, что ни один из них не сработал.
Chrome не попытается перейти без тапа
Даже правильно построенный intent://-URL, нацеленный на нужный пакет без преждевременного fallback, зависит от того, как его запускают. Chrome требует пользовательский жест, чтобы обработать intent://-навигацию. Запустите её автоматически, при загрузке страницы, из скрипта, из цепочки редиректов без клика посередине, и Chrome вообще не попытается открыть приложение. Он сразу пойдёт на S.browser_fallback_url.
Снаружи страницы этот результат неотличим от того, что приложение не установлено. Ваши логи, ваша fallback-посадочная, всё ниже по потоку выглядит одинаково, действительно ли приложения нет или Chrome просто отказался пробовать, потому что посетитель ничего не сделал, что запустило бы переход. Это та деталь, вокруг которой стоит проектировать весь флоу: автоматическая попытка ненадёжна не в смысле «работает по большей части». Это шаг, который никогда не запускается. Тап — не опциональное украшение; это предпосылка для всего механизма.
Это ещё и та деталь, которую QA обычно упускает, потому что ручное тестирование почти целиком состоит из тапов. Тестировщик, открывающий ссылку рукой, каждый раз удовлетворяет требование жеста, поэтому флоу, который запускает переход автоматически посреди цепочки редиректов, может пройти каждый ручной тест и всё равно провалиться на реальном трафике в тот момент, когда какой-то шаг в продакшне не происходит из прямого тапа.
Webview — это другая комната, а не Chrome поменьше
Не каждый клик попадает в Chrome. Ссылка, открытая в собственном in-app браузере другого приложения — в webview, которым Instagram, Facebook или TikTok оборачивают исходящие ссылки, — это отдельная среда рендеринга. Требование жеста выше задокументировано именно для Chrome. Что делает webview, получив ту же самую intent://-ссылку, эта же документация не описывает. Routing, проверенный только в Chrome, тестируется в одной комнате из нескольких, через которые на самом деле приходит трафик.
Доказательство того, что успешный переход не гарантирует довольного посетителя
Даже когда всё выше сделано правильно, приложение-цель всё равно может отправить посетителя прямиком обратно в браузер. Проверено на реальном устройстве Android 15 (Pixel 8, Chrome 131):
Intent-ссылка в Instagram, intent://www.instagram.com/instagram/#Intent;scheme=https;package=com.instagram.android;end, разрешилась чисто. Android передал её собственному com.instagram.url.UrlHandlerActivity Instagram, и приложение открылось прямо на нужном профиле и осталось там.
Intent-ссылка в TikTok, построенная так же, тоже разрешилась правильно. Android доставил её в com.ss.android.ugc.aweme.deeplink.AppLinkHandlerV2, точно как задумано. Системный лог показал, что задача создана с прикреплённой deep link, а затем закрылась со значением numActivities=0, и посетитель оказался снова в браузере, на исходной веб-странице. TikTok был разлогинен, и ему было нечего рендерить для этой ссылки. Routing сработал. Приложение отказалось показать контент.
Успешный переход и посетитель, который всё равно оказывается в браузере, — не взаимоисключающие вещи, и ничто на стороне отправителя этому не помешает. Если ваша метрика — передала ли ОС клик приложению, это в вашей власти. Если ваша метрика — остался ли посетитель в приложении, это зависит от состояния логина и решения о рендеринге внутри кода, который вам не принадлежит.
Что контролируемо, а что нет
| Уровень | Кто решает | Можно ли контролировать |
|---|---|---|
| Какой пакет получает клик | Ваше построение intent:// | Да |
| Сработает ли fallback преждевременно | Расположение вашего fallback, только последний шаг | Да |
| Будет ли вообще попытка перехода | Требование жеста в Chrome | Да, запускайте её реальным тапом |
| Chrome или in-app webview | Где посетитель тапнул ссылку | Нет |
| Установлен ли пакет-цель | Устройство посетителя | Нет |
| Что покажет приложение после открытия | Собственное состояние логина и рендеринга приложения | Нет |
Первые три строки — это инженерия. Постройте цепочку правильно, держите fallback подальше от каждого шага-кандидата, кроме последнего, и никогда не запускайте переход автоматически. Стрим, построенный так, а логика редиректа DarkCore применяет то же самое правило fallback в конце и порядок пакетов для каждого приложения, доводит каждого посетителя, которого можно довести, до момента перехода. Последние три строки — это вообще не проблема routing. Ни один fallback URL, ни одна логика повтора и ни одна более изощрённая строка intent:// не изменит того, что происходит, когда ОС уже передала клик приложению.
FAQ
Исправляет ли добавление ещё пакетов в цепочку откат в стиле TikTok?
Нет. Тот откат произошёл уже после того, как Android правильно передал клик TikTok. Приложению было нечего рендерить, потому что оно было разлогинено. Больше пакетов помогает посетителю с другой сборкой приложения добраться до нужной. Они не меняют того, что делает принимающее приложение, когда клик уже у него.
Если посетитель оказывается на fallback, значит ли это, что приложение не установлено?
Не обязательно. Это означает либо что приложения нет, либо что переход так и не попытались совершить, потому что он запустился без пользовательского жеста. Оба варианта дают одинаковую fallback-посадочную, и ничто в ваших логах их не различает.
Действует ли правило порядка fallback, если я нацелен только на один пакет?
Риск, от которого оно защищает, специфичен именно для цепочек с более чем одним кандидатом. С одним пакетом выталкивать посетителя преждевременно не от чего, потому что fallback на этом единственном шаге сработает только после того, как этот пакет уже исключён. Правило становится важным в тот момент, когда вы добавляете второго кандидата, чтобы покрыть другую сборку того же приложения.