В августе 2026 года разработчики приложения доставки продуктов подключили SDK AppsFlyer за два дня и отчитались: first_purchase и purchase уходят, сумма в тенге. Через неделю маркетолог сверяет AppsFlyer с системой заказов и находит три расхождения. Первых заказов в MMP больше, чем новых клиентов в базе. Заказов, оплаченных курьеру, в MMP нет совсем. А один заказ на 9 000 ₸ записан дважды (номера и суммы в уроке условные). SDK при этом работал без сбоев. Сломались договорённости, которые никто не записал: когда отправлять событие, откуда приложение знает, что заказ первый, и что делать с оплатой, которая приходит через три часа после оформления.
Что делает SDK и чего он не знает
SDK MMP - библиотека, которую разработчик встраивает в приложение. Первый запуск, сессии и рекламный идентификатор она отправляет сама, а бизнес-событие - только когда приложение вызовет её метод. Когда его вызвать, библиотека не знает: она не видит ни платёжный шлюз, ни историю клиента, ни приложение курьера. Всё это знает бэкенд.
Поэтому событие с деньгами описывают, начиная с факта в системе заказов, а SDK остаётся одним из способов его доставить. Сам SDK, ключи и связь MMP с рекламными сетями настраивают по шагам из MMP на практике. Здесь речь о том, что отправлять и как принять работу.
За чужую библиотеку отвечает тот, кто выпускает приложение. Apple просит указать в App Store Connect все данные, которые собирает приложение, включая данные сторонних SDK, и прямо пишет, что разработчик отвечает за весь код в своём приложении (правила Apple о данных пользователей). Если приложение показывает окно ATT, SDK настраивают ждать ответа. Имени, телефона и адреса доставки в параметрах событий нет: MMP нужны номер заказа и сумма, а не человек.
Одна оплата - одно событие
У сервиса два способа оплаты. Картой в приложении: банк подтверждает платёж за секунды, пока человек ещё смотрит на экран. И курьеру при доставке: заказ становится оплаченным, когда курьер закрывает его в своём приложении, через несколько часов после оформления. Покупка - это оплаченный заказ, и на каждый такой заказ уходит ровно одно событие:
first_purchase, если это первый оплаченный заказ клиента;purchaseна каждый следующий.
Первый заказ определяет бэкенд по истории клиента, а не приложение по своей отметке. Отметка живёт в памяти телефона и пропадает при переустановке, а у клиента может быть второй телефон. Так в примере и набежали лишние первые заказы: постоянная покупательница переставила приложение, и её десятый заказ ушёл как first_purchase.
Выручкой AppsFlyer считает только параметр af_revenue, причём число без пробелов, запятых и знака валюты (выручка в событиях AppsFlyer). Валюту передают в af_currency кодом KZT, иначе AppsFlyer примет сумму за доллары. af_revenue ставят только в события, которые и есть деньги: если положить его и в «оформил заказ», и в purchase, выручка одного заказа посчитается дважды. Отмену после оплаты AppsFlyer принимает как отрицательную сумму. В Adjust событие сначала заводят в интерфейсе и получают токен, а сумму передают методом setRevenue с валютой KZT (события в SDK Adjust).
Двойной заказ на 9 000 ₸ появился иначе. Событие висело на показе экрана «Заказ оплачен», а покупательница через минуту открыла этот экран снова из истории заказов. AppsFlyer сам отбрасывает только одинаковое событие, пришедшее в пределах 10 секунд, а одинаковым считается событие с тем же именем, значением, приложением и AppsFlyer ID (как AppsFlyer убирает дубли). Всё, что позже, на совести приложения: событие отправляют по ответу сервера об оплате и один раз на номер заказа, а не при каждом показе экрана. У Adjust для этого есть setDeduplicationId, и по умолчанию SDK помнит последние 10 таких номеров.
Связь тоже может пропасть в момент отправки. SDK AppsFlyer держит в очереди до 40 событий и отправляет их по одному, когда связь вернётся, а сверх этого события теряются. Для приложения, где покупка случается раз в несколько дней, запаса хватает, но это ещё одна причина считать деньги по бэкенду, а не по MMP.
Карточка события для разработчиков
Разработчику отдают не строку «добавьте покупку», а карточку, как в спецификации dataLayer: что событие означает, когда уходит и когда нет, с какими параметрами и где его увидеть. У одного события здесь два пути доставки, поэтому в карточке две колонки.
| Поле | Оплата картой в приложении | Оплата курьеру |
|---|---|---|
| Событие | first_purchase или purchase по флагу первого заказа из ответа сервера | first_purchase или purchase по истории клиента в базе |
| Кто отправляет | SDK в приложении | Бэкенд, запросом S2S в AppsFlyer |
| Когда | Сервер подтвердил оплату, приложение получило ответ | Курьер закрыл заказ как оплаченный |
| Когда не отправляется | Нажатие «Оформить», отказ банка, повторный показ экрана «Заказ оплачен» | Отмена, отказ от заказа у двери, повторный запуск задачи отправки |
| Параметры | af_revenue = итог заказа числом, af_currency = KZT, af_order_id = номер заказа | eventValue с af_revenue и af_order_id, eventCurrency = KZT |
| Устройство | SDK знает сам | appsflyer_id, GAID в advertising_id (на iPhone idfa, статус ATT в att и idfv) и версия системы, сохранённые на сервере заранее |
| Время | Момент отправки | eventTime = время оплаты в UTC |
| Повтор | Одно событие на номер заказа, дубль в пределах 10 секунд AppsFlyer уберёт сам | AppsFlyer S2S-события не склеивает: у заказа отметка «отправлено», повтор только после ошибки |
| Где проверить | Live Events на тестовом телефоне | Live Events, если в запросе есть GAID или IDFA тестового телефона |
| Кто отвечает | Мобильный разработчик, проверка - аналитик | Бэкенд-разработчик, проверка - аналитик |
af_order_id дубли не убирает, AppsFlyer по нему ничего не склеивает. Зато по нему заказ из сырой выгрузки MMP находится в системе заказов, и сверка идёт по номерам, а не по суммам за день.
Заказы, оплаченные курьеру: событие с сервера
Курьер закрывает заказ через три часа после оформления. Приложение покупателя к этому времени давно закрыто, и SDK отправлять нечего. Поэтому такую покупку отправляет бэкенд через S2S API AppsFlyer: запрос POST на https://api3.appsflyer.com/inappevent/<app_id> с ключом S2S в заголовке authentication. S2S отправляют только для оплаты курьеру: если бэкенд отправит и заказы по карте, они задвоятся, а AppsFlyer S2S-события не склеивает. Старый адрес api2 и ключ разработчика для этого AppsFlyer выводит из работы, так что из старых инструкций их не копируют. Тело запроса для заказа 2041 на 9 000 ₸, сделанного с Android-телефона:
appsflyer_idобязателен. Приложение берёт его у SDK методомgetAppsFlyerUIDи отправляет на сервер, когда человек входит в аккаунт, а бэкенд хранит его в карточке клиента. Если этого не сделать заранее, серверное событие не к чему будет привязать.advertising_id- GAID телефона. AppsFlyer предупреждает, что без него постбэки в SRN могут не уйти: заказ попадёт в MMP, но не в Meta и Google Ads (поля S2S API). На iPhone вместо него передаютidfaи статус ATT вatt, а для Aggregated Event Measurement Meta ещёidfvиip:idfvAppsFlyer сам не подставит. IDFA бывает пустым, например если человек отказал в отслеживании.eventValue- строка, внутри которой JSON с экранированными кавычками. Если отправить туда объект, AppsFlyer не запишет выручку.eventTime- время оплаты в UTC: курьер закрыл заказ в 14:05 по Алматы, это 09:05 UTC. AppsFlyer поставит это время, если событие пришло в тот же день или на следующий до 02:00 UTC, а позже запишет время прихода запроса.os- версия системы телефона, здесь Android 14. AppsFlyer требует её на любой платформе, а на iOS событие без неё считается пришедшим с iOS 14.5, и от этого зависит, какие сырые данные по нему будут доступны.
Серверные события AppsFlyer не склеивает вовсе. Если задача отправки упала уже после успешного запроса и перезапустилась, покупка задвоится, поэтому у заказа в базе ставят отметку «отправлено» и повторяют запрос, только когда AppsFlyer ответил ошибкой.
По appsflyer_id AppsFlyer находит установку и привязывает к ней покупку. Заказ, оплаченный курьеру, получает ту же кампанию, что и установка, а заказ органического пользователя остаётся органическим. Так в MMP появились заказы, которых в начале урока не было вовсе.
У Adjust серверное событие уходит на https://s2s.adjust.com/event с s2s=1, токенами приложения и события, идентификатором устройства adid, суммой и валютой. События старше 58 дней Adjust не принимает, а параметр environment по умолчанию равен production, поэтому тестовые запросы помечают sandbox (S2S API Adjust). Как тот же поток устроен у банка, где деньги появляются в учётной системе через несколько дней, описано в статье про AppsFlyer, Adjust и Firebase.
Тест на одном телефоне
Работу разработчиков принимают на одном телефоне, в AppsFlyer на странице SDK Integration Tests. В её разделе Live Events события с тестового устройства видны сразу, вместе с параметрами.
- Соберите тестовую версию с логами SDK: у AppsFlyer это
isDebug, у Adjust окружениеsandbox. Перед выпуском логи выключают, а Adjust переводят вproduction. - Зарегистрируйте телефон как тестовое устройство: через приложение My Device ID by AppsFlyer или вручную по GAID или IDFA в Settings → Test Devices. Без регистрации AppsFlyer запишет только первую установку, а повторные в течение 90 дней сочтёт переустановками и новыми не засчитает. На iPhone разрешите тестовому приложению отслеживание: страница тестов работает, только когда SDK получил идентификатор.
- Удалите приложение, откройте Settings → SDK Integration Tests → Live Events, выберите приложение и телефон, нажмите Continue, затем поставьте приложение и запустите его.
- Оплатите картой первый заказ на 9 000 ₸. В Live Events должен прийти
first_purchaseсaf_revenue9000,af_currencyKZT и номером заказа. - Откройте этот заказ из истории и перезапустите приложение. Нового события быть не должно.
- Оплатите второй заказ: придёт
purchase. - Переустановите приложение и оплатите третий заказ. Снова должен прийти
purchase, а неfirst_purchase. - Оформите заказ с оплатой курьеру и попросите бэкенд закрыть его как оплаченный. Событие появится в Live Events, если в запросе есть GAID или IDFA тестового телефона.
В Adjust то же делают в Testing Console: вводят рекламный ID телефона и смотрят его установку и события, а кнопка Forget device стирает устройство, чтобы пройти тест заново.
Исправление в SDK доходит до людей только с новой версией приложения, и пока они не обновились, старые версии шлют события по-старому. Поэтому после выпуска первые заказы в MMP сверяют с новыми клиентами в базе отдельно по платформам и версиям приложения, а не одной общей цифрой.
Упражнение: карточка для своего приложения
Возьмите приложение, с которым работаете, или сервис доставки из урока и подготовьте одну страницу для разработчиков.
Выполнено 0 из 5
Начните разбор со строки «Когда». Если там написано «на экране успеха», вернитесь к вопросу, кто первым узнаёт об оплате: экран успеха показывают и после отказа банка, и при открытии заказа из истории. У сервиса доставки первым узнаёт сервер, которому пришло подтверждение от банка или от приложения курьера, и от этого зависят обе колонки карточки.
Потом посмотрите на строку «Устройство» в колонке S2S. Если неясно, откуда сервер возьмёт appsflyer_id, серверное событие не к чему будет привязать, и путь S2S придётся отложить до версии приложения, которая начнёт этот ID отправлять. Тест готов, когда в нём есть повторный показ экрана успеха, переустановка и, если есть путь S2S, заказ, подтверждённый при закрытом приложении. Эти три шага ловят все три расхождения из начала урока.
Видео: что такое SDK
Короткое объяснение термина на английском.