Пользователь нажимает на вашу ссылку — товар, которым поделились, приглашение, сброс пароля — а приложение ещё не установлено. Он попадает в App Store или Google Play, устанавливает приложение, открывает его и… оказывается на обычном главном экране. Контекст того, на что он нажал, потерян. Именно этот разрыв в пользовательском опыте призван устранить , и долгие годы большинство команд решало это через Firebase Dynamic Links. Этого варианта больше нет.

В статье разбираем, как мы построили систему отложенного deep linking, которая переживает установку — и при необходимости умеет дождаться бизнес-события вроде логина, прежде чем выполнить переход, — используя iOS и Google Play API вместо стороннего сервиса.

Ключевые выводы

  • Отложенный deep linking сохраняет цель ссылки через установку приложения, чтобы первый запуск открывал нужный экран, а не главный.
  • Firebase Dynamic Links объявлен устаревшим и отключён 25 августа 2025 года — самое распространённое решение исчезло, и существующие ссылки больше не работают.
  • Популярные самодельные обходные пути — фингерпринтинг устройства и сопоставление через буфер обмена — ненадёжны, сомнительны с точки зрения приватности и не одобряются Apple.
  • На iOS лёгкий App Clip перехватывает ссылку ещё до того, как появится полное приложение, и передаёт её через общий контейнер App Group после установки.
  • На Android официальный Play Install Referrer API детерминированно доставляет полезную нагрузку ссылки при первом запуске.
  • Очередь отложенных ссылок позволяет отложить переход до тех пор, пока не завершится установка и не наступит опциональное бизнес-событие (например, успешный логин).

Что такое отложенный deep linking?

Deep link открывает конкретный экран внутри приложения — например myapp://product/42. Deep link работает, только если приложение уже установлено. Отложенный deep linking снимает это условие: когда приложение не установлено, цель запоминается на протяжении визита в стор и установки, и приложение переходит к ней при первом запуске.

Самое сложное — это разрыв. Между нажатием и первым запуском операционная система устанавливает новое приложение, которое ничего не знает о том, на что нажал пользователь. Пронести полезную нагрузку через этот разрыв — надёжно и без вторжения в приватность — и есть вся задача.

Большую часть последнего десятилетия ответом по умолчанию был Firebase Dynamic Links. Он брал на себя редирект в стор, отложенную полезную нагрузку и разрешение при первом запуске для обеих платформ через один SDK.

Google объявил Dynamic Links устаревшим и полностью отключил сервис 25 августа 2025 года. Ссылки перестали разрешаться, и готовой замены от Google нет. Платные платформы атрибуции вроде Branch и AppsFlyer закрывают эту нишу коммерчески, но многие команды хотят владеть процессом без счёта за каждое событие и без отправки кликов пользователей третьей стороне.

Firebase Dynamic Links больше не вариант

Если ваш отложенный deep linking всё ещё зависит от Firebase Dynamic Links, он перестал работать 25 августа 2025 года. Переход на Universal Links и Android App Links закрывает случай установленного приложения — но не отложенный случай (когда приложение ещё не установлено). Именно этот пробел и закрывает наш подход.

Распространённые самодельные обходные пути — и их подводные камни

Когда Dynamic Links отпадает, большинство руководств хватается за один из двух самодельных приёмов. Их стоит понимать именно потому, что они хрупкие.

Фингерпринтинг устройства. Веб-страница записывает «отпечаток» (IP-адрес, размер экрана, версия ОС, локаль) в момент нажатия. При первом запуске приложение отправляет собственный отпечаток на сервер, который пытается сопоставить их в коротком временном окне. Подводные камни серьёзны: общие/операторские IP вызывают несовпадения, окно сопоставления короткое, точность падает в нагруженных сетях, а вероятностное сопоставление пользователей — ровно тот паттерн, против которого направлены правила приватности Apple.

Буфер обмена. Веб-страница копирует полезную нагрузку в буфер обмена; приложение читает её при запуске. Начиная с iOS 14 это вызывает видимый баннер «вставлено из Safari», пользователь может очистить буфер, а любое копирование в промежутке уничтожает полезную нагрузку.

ПодходПлатформаНадёжностьПриватностьСтатус
Firebase Dynamic LinksiOS + AndroidВысокаяПриемлемаяОтключён в авг. 2025
Фингерпринтинг устройстваiOS + AndroidНизкая–средняяПлохаяНе рекомендуется
Буфер обменаiOSСредняяПоказывает баннер вставкиХрупкий
App Clip + App Group (наш)iOSВысокаяХорошаяРекомендуется
Play Install Referrer APIAndroidВысокаяХорошаяОфициальный

Наш подход: App Clips на iOS, Install Referrer на Android

Вместо того чтобы угадывать, кто пользователь, уже после установки, мы перехватываем ссылку детерминированно на каждой платформе нативным механизмом, а затем сходимся к единому шагу разрешения внутри приложения.

iOS: перехват ссылки через App Clip

App Clip — это крошечная часть вашего приложения (до 15 МБ), которая почти мгновенно запускается по ссылке, App Clip Code или QR-коду — без полной установки. Это свойство нам и нужно: App Clip выполняется до того, как появится полное приложение, поэтому он видит исходную ссылку.

Как это работает:

  1. Ссылка открывает App Clip. iOS доставляет URL вызова через NSUserActivity.
  2. App Clip записывает этот URL (и метку времени) в общий контейнер App Group, который может читать и полное приложение.
  3. App Clip показывает оверлей App Store с предложением установить полное приложение.
  4. После установки полное приложение читает отложенную ссылку из того же контейнера App Group при первом запуске.
// App Clip — перехват URL вызова в общий App Group
func scene(_ scene: UIScene, continue activity: NSUserActivity) {
    guard activity.activityType == NSUserActivityTypeBrowsingWeb,
          let url = activity.webpageURL else { return }

    let shared = UserDefaults(suiteName: "group.pro.nerdy.deeplink")
    shared?.set(url.absoluteString, forKey: "pendingDeepLink")
    shared?.set(Date(), forKey: "pendingDeepLinkAt")
}
// Полное приложение — читаем один раз, при первом запуске после установки
let shared = UserDefaults(suiteName: "group.pro.nerdy.deeplink")
if let link = shared?.string(forKey: "pendingDeepLink") {
    DeepLinkQueue.shared.enqueue(link)
    shared?.removeObject(forKey: "pendingDeepLink")
}

Поскольку App Clip и полное приложение используют общий App Group, полезная нагрузка передаётся точно — без сопоставления отпечатков, без баннера буфера обмена, без вероятностных догадок.

Android: Google Play Install Referrer API

У Android есть чистый официальный ответ: Play Install Referrer API. Прикрепите свою полезную нагрузку к URL Play Store как параметр referrer, и Google доставит эту строку приложению при первом запуске.

https://play.google.com/store/apps/details?id=pro.nerdy.app&referrer=deeplink%3D%2Fproduct%2F42
val client = InstallReferrerClient.newBuilder(context).build()
client.startConnection(object : InstallReferrerStateListener {
    override fun onInstallReferrerSetupFinished(responseCode: Int) {
        if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
            val referrer = client.installReferrer.installReferrer // "deeplink=/product/42"
            DeepLinkQueue.enqueue(referrer)
            client.endConnection()
        }
    }
    override fun onInstallReferrerServiceDisconnected() {}
})

Это детерминированно — строка referrer переживает визит в стор и установку нетронутой, без серверного сопоставления.

Сходимся в одном месте — и ждём логина

Обе платформы теперь пишут в одну и ту же внутреннюю очередь отложенных ссылок. Именно это мы построили для нашей платформы кэшбэка и лояльности, и в нашей разработке приложений на Flutter мы делаем это через небольшой platform channel, который прокидывает нативную полезную нагрузку в Dart, а затем единый резолвер решает, когда действовать.

«Когда» здесь важно. Deep link на аутентифицированный экран (/orders/42) упадёт при холодном старте, если пользователь ещё не залогинен. Поэтому резолвер не переходит сразу — он удерживает цель до тех пор, пока приложение не будет готово и не наступит требуемое бизнес-событие.

class DeepLinkQueue {
  Uri? _pending;
  bool _authReady = false;

  void enqueue(Uri link) { _pending = link; _tryResolve(); }

  // Вызывается бизнес-слоем, например после успешного логина
  void onEvent(AppEvent event) {
    if (event == AppEvent.loggedIn) _authReady = true;
    _tryResolve();
  }

  void _tryResolve() {
    final link = _pending;
    if (link == null || !_authReady) return; // ждём оба условия
    _pending = null;
    router.go(link.path); // безопасно: приложение установлено И пользователь залогинен
  }
}

Результат — отложенный deep link, устойчивый к двум сценариям отказа, которые ломают большинство реализаций: приложение не установлено и целевой экран не готов.

Когда этот подход уместен

Этот паттерн хорош, когда вы контролируете и источник ссылки, и приложение, хотите нативной надёжности и заботитесь о приватности или стоимости вендора. Если нужен только случай установленного приложения, достаточно обычных Universal Links и App Links. Если нужна кросс-канальная маркетинговая атрибуция с дашбордами, платная платформа вроде Branch может оправдать затраты. Но для продуктовых сценариев — приглашения, контент, которым делятся, онбординг, сброс пароля — App Clips плюс Install Referrer API дают детерминированный отложенный deep link, которым вы полностью владеете.

Если вы мигрируете с Firebase Dynamic Links или строите это с нуля, мы поможем вам это запустить.

Часто задаваемые вопросы

Отложенный deep linking сохраняет цель ссылки через установку приложения. Когда пользователь нажимает ссылку без установленного приложения, цель запоминается, пользователь устанавливает приложение, и при первом запуске приложение переходит к исходной цели вместо обычного главного экрана.
Google объявил Firebase Dynamic Links устаревшим и отключил сервис 25 августа 2025 года. Существующие ссылки перестали разрешаться, поэтому любому продукту, который на него полагался, нужна замена для отложенного deep linking.
App Clip мгновенно запускается по ссылке без полной установки, перехватывает URL вызова и записывает его в общий контейнер App Group. После того как пользователь устанавливает полное приложение, оно читает сохранённую ссылку при первом запуске и направляет пользователя к исходной цели.
Да. Install Referrer API — это официальный API Google, который доставляет строку referrer, прикреплённую к ссылке Play Store, приложению при первом запуске. Он детерминирован, в отличие от фингерпринтинга устройства, поэтому полезная нагрузка deep link приходит нетронутой.
Да. Сохраните разрешённую ссылку в очереди ожидания и выполняйте переход только после наступления требуемого бизнес-события, например успешного логина. Это позволяет направлять пользователей на аутентифицированные экраны, которые иначе упали бы при холодном старте.