Архитектура офферных событий в продуктовых сценариях

Документация по Product Flow с примерами интеграции в SDK:

Подробнее о подходе

Product Flow — это новая схема работы типовых событий AppMetrica для приложений с офферами, покупками, оформлением заявок и многошаговыми сценариями оформления продуктов и услуг.

Данный подход помогает стандартизировать аналитику следующих сущностей и сценариев:

  • продуктовые предложения;
  • маркетинговые офферы;
  • покупки товаров и услуг;
  • пользовательские воронки;
  • шаги оформления;
  • заявки и результаты одобрения;
  • асинхронные S2S-результаты.

Рекомендуем использовать подход Product Flow для следующих типов приложений:

  • банки;
  • страховые сервисы;
  • телеком-приложения;
  • сервисы бронирования и путешествий;
  • подписочные сервисы;
  • сервисы доставки;
  • маркетплейсы;
  • игровые приложения;
  • любые приложения с продуктовыми сценариями, покупками и аналитикой воронок.

Не расходуют лимиты custom events и DSA

Новые типовые события не учитываются в следующих лимитах:

  • custom events;
  • Data Stream API.

Это позволяет:

  • постепенно внедрять новую схему разметки;
  • отправлять больше продуктовых сценариев и шагов;
  • строить подробные воронки и аналитику без расходования лимитов кастомных событий и DSA.

Какую проблему решает данный подход

В настоящее время многие приложения используют custom events для передачи аналитических данных о следующих сущностях и сценариях:

  • офферы;
  • заявки;
  • продуктовые сценарии;
  • onboarding-сценарии;
  • сценарии оформления заказа;
  • сценарии одобрения.

Такой подход приводит к следующим проблемам:

  • одинаковые сценарии размечаются по-разному;
  • затруднено построение типовых отчетов;
  • аналитика слабо переносится между командами;
  • невозможно автоматическое построение воронок;
  • сложно сравнивать эффективность офферов и сценариев.

Схема продуктовых сценариев и офферных событий предлагает унифицированный подход к разметке ключевых этапов:

  • показ оффера;
  • начало оформления;
  • промежуточные шаги;
  • финальный результат.

Основные события

Схема взаимодействия строится на основе четырех основных событий:

Событие

Что означает

offerShown

Пользователь увидел оффер или предложение

flowStart

Пользователь начал оформление продукта

flowStep

Промежуточный шаг оформления

flowResult

Итоговый результат

Сценарии разметки

В этом разделе показаны полные пользовательские сценарии для разных индустрий.

Примеры сценариев

Все сценарии в данном документе являются примерами разметки и демонстрируют возможные варианты интеграции.

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

Каждый сценарий описывает:

  • что видит и делает пользователь в приложении;
  • какие события нужно отправить;
  • когда offerShown можно не отправлять;
  • когда flowStart можно не отправлять;
  • как передавать промежуточные шаги через flowStep;
  • как передавать финальный результат через flowResult;
  • как использовать payload, referrer и stepOption;
  • как передавать результат позже через S2S.

Банки и финтех

Кредит по персональному офферу: offer → оформление → pending → S2S result

Пользователь видит баннер: «Вам предварительно одобрен кредит до 500 000 ₽ по ставке от 12.5%».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("credit_q1", "financial_product")
        .withProductId("personal_loan")
        .withBenefitType("discount_percentage")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("banner")
                    .withIdentifier("main_screen_credit_banner")
                    .withScreen("main")
                    .build()
        )
        .withPayload(mapOf(
            "campaign_id" to "credit_spring_2026",
            "segment" to "preapproved_users",
            "approved_limit" to "500000",
            "currency" to "RUB",
            "interest_rate" to "12.5"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "credit_q1", offerType: "financial_product")
    .withProductId("personal_loan")
    .withBenefitType("discount_percentage")
    .withReferrer(OfferReferrer(type: "banner", identifier: "main_screen_credit_banner", screen: "main"))
    .withPayload([
        "campaign_id": "credit_spring_2026",
        "segment": "preapproved_users",
        "approved_limit": "500000",
        "currency": "RUB",
        "interest_rate": "12.5"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь нажимает на баннер и начинает оформление заявки.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("personal_loan")
        .withProductOfferId("credit_q1")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "personal_loan")
    .withProductOfferId("credit_q1")
    .build()

AppMetrica.report(event: event)
Пользователь загружает документы.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("personal_loan", "documents")
        .withProductOfferId("credit_q1")
        .withStepOption("passport_upload")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "personal_loan", stepType: "documents")
    .withProductOfferId("credit_q1")
    .withStepOption("passport_upload")
    .build()

AppMetrica.report(event: event)
Пользователь отправляет заявку на скоринг.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("personal_loan", "scoring")
        .withProductOfferId("credit_q1")
        .withStepOption("automatic_scoring")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "personal_loan", stepType: "scoring")
    .withProductOfferId("credit_q1")
    .withStepOption("automatic_scoring")
    .build()

AppMetrica.report(event: event)
Пользователь завершил действия на своей стороне, заявка ушла на рассмотрение.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.PENDING, "personal_loan")
        .withProductOfferId("credit_q1")
        .withPayload(mapOf(
            "application_id" to "loan_app_12345",
            "requested_amount" to "450000",
            "currency" to "RUB"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "personal_loan", status: .pending)
    .withProductOfferId("credit_q1")
    .withPayload([
        "application_id": "loan_app_12345",
        "requested_amount": "450000",
        "currency": "RUB"
    ])
    .build()

AppMetrica.report(event: event)
Позже банк присылает финальный результат через S2S.
flowResult(
    productId = "travel_insurance",
    productOfferId = "travel_insurance_offer",
    status = "success",
    payload = mapOf(
        "application_id" to "insurance_app_987",
        "policy_id" to "policy_555",
        "decision_source" to "s2s"
    )
)

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

Органическое оформление кредита без оффера

Пользователь сам открыл раздел «Кредиты» из меню приложения. Оффера не было.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("personal_loan")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "personal_loan")
    .build()

AppMetrica.report(event: event)
Пользователь загружает документы.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("personal_loan", "documents")
        .withStepOption("passport_upload")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "personal_loan", stepType: "documents")
    .withStepOption("passport_upload")
    .build()

AppMetrica.report(event: event)
Заявка отправлена на рассмотрение.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.PENDING, "personal_loan")
        .withPayload(mapOf(
            "application_id" to "loan_app_67890",
            "requested_amount" to "300000",
            "currency" to "RUB"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "personal_loan", status: .pending)
    .withPayload([
        "application_id": "loan_app_67890",
        "requested_amount": "300000",
        "currency": "RUB"
    ])
    .build()

AppMetrica.report(event: event)
Позже банк присылает отказ через S2S.
flowResult(
    productId = "personal_loan",
    productOfferId = null,
    status = "declined",
    payload = mapOf(
        "application_id" to "loan_app_67890",
        "decline_reason" to "scoring_rejected",
        "decision_source" to "s2s"
    )
)

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

Телеком

Подключение eSIM по офферу

Пользователь открывает экран тарифов и видит баннер: «eSIM со скидкой 20% на первый месяц».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("esim_offer_q2", "service")
        .withProductId("esim")
        .withBenefitType("discount_percentage")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("banner")
                    .withIdentifier("tariffs_screen_esim_banner")
                    .withScreen("tariffs")
                    .build()
        )
        .withPayload(mapOf(
            "campaign_id" to "esim_discount_q2",
            "discount_value" to "20",
            "discount_period" to "first_month",
            "region" to "RS"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "esim_offer_q2", offerType: "service")
    .withProductId("esim")
    .withBenefitType("discount_percentage")
    .withReferrer(OfferReferrer(type: "banner", identifier: "tariffs_screen_esim_banner", screen: "tariffs"))
    .withPayload([
        "campaign_id": "esim_discount_q2",
        "discount_value": "20",
        "discount_period": "first_month",
        "region": "RS"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь нажимает на баннер и начинает подключение.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("esim")
        .withProductOfferId("esim_offer_q2")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "esim")
    .withProductOfferId("esim_offer_q2")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает тариф.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("esim", "tariff_selection")
        .withProductOfferId("esim_offer_q2")
        .withStepOption("unlimited_30d")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "esim", stepType: "tariff_selection")
    .withProductOfferId("esim_offer_q2")
    .withStepOption("unlimited_30d")
    .build()

AppMetrica.report(event: event)
Пользователь проходит идентификацию.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("esim", "identity_verification")
        .withProductOfferId("esim_offer_q2")
        .withStepOption("document_scan")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "esim", stepType: "identity_verification")
    .withProductOfferId("esim_offer_q2")
    .withStepOption("document_scan")
    .build()

AppMetrica.report(event: event)
eSIM успешно подключена.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "esim")
        .withProductOfferId("esim_offer_q2")
        .withPayload(mapOf(
            "tariff_id" to "unlimited_30d",
            "activation_type" to "esim",
            "discount_applied" to "true"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "esim", status: .success)
    .withProductOfferId("esim_offer_q2")
    .withPayload([
        "tariff_id": "unlimited_30d",
        "activation_type": "esim",
        "discount_applied": "true"
    ])
    .build()

AppMetrica.report(event: event)

Подключение роуминг-пакета перед поездкой

Пользователь открывает раздел роуминга и видит предложение: «10 ГБ в Европе на 14 дней за 1500 рублей».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("eu_roaming_10gb", "service")
        .withProductId("roaming_package")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("banner")
                    .withIdentifier("roaming_europe_banner")
                    .withScreen("roaming")
                    .build()
        )
        .withPayload(mapOf(
            "region" to "Europe",
            "traffic_limit_gb" to "10",
            "duration_days" to "14",
            "price" to "1500",
            "currency" to "RUB"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "eu_roaming_10gb", offerType: "service")
    .withProductId("roaming_package")
    .withReferrer(OfferReferrer(type: "banner", identifier: "roaming_europe_banner", screen: "roaming"))
    .withPayload([
        "region": "Europe",
        "traffic_limit_gb": "10",
        "duration_days": "14",
        "price": "1500",
        "currency": "RUB"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает подключение роуминг-пакета.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("roaming_package")
        .withProductOfferId("eu_roaming_10gb")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "roaming_package")
    .withProductOfferId("eu_roaming_10gb")
    .build()

AppMetrica.report(event: event)
Пользователь подтверждает условия подключения.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("roaming_package", "terms_confirmation")
        .withProductOfferId("eu_roaming_10gb")
        .withStepOption("accepted")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "roaming_package", stepType: "terms_confirmation")
    .withProductOfferId("eu_roaming_10gb")
    .withStepOption("accepted")
    .build()

AppMetrica.report(event: event)
Роуминг-пакет успешно подключён.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "roaming_package")
        .withProductOfferId("eu_roaming_10gb")
        .withPayload(mapOf(
            "region" to "Europe",
            "traffic_limit_gb" to "10",
            "duration_days" to "14",
            "activation_status" to "enabled"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "roaming_package", status: .success)
    .withProductOfferId("eu_roaming_10gb")
    .withPayload([
        "region": "Europe",
        "traffic_limit_gb": "10",
        "duration_days": "14",
        "activation_status": "enabled"
    ])
    .build()

AppMetrica.report(event: event)

Upgrade тарифа

Пользователь видит предложение: «Перейдите на Premium-тариф с безлимитным интернетом».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("premium_tariff_upgrade", "service")
        .withProductId("tariff_upgrade")
        .withBenefitType("bundle_extra")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("popup")
                    .withIdentifier("premium_upgrade_popup")
                    .withScreen("tariff_details")
                    .build()
        )
        .withPayload(mapOf(
            "current_tariff" to "smart_m",
            "target_tariff" to "premium_unlimited",
            "monthly_price" to "1990",
            "currency" to "RUB"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "premium_tariff_upgrade", offerType: "service")
    .withProductId("tariff_upgrade")
    .withBenefitType("bundle_extra")
    .withReferrer(OfferReferrer(type: "popup", identifier: "premium_upgrade_popup", screen: "tariff_details"))
    .withPayload([
        "current_tariff": "smart_m",
        "target_tariff": "premium_unlimited",
        "monthly_price": "1990",
        "currency": "RUB"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает смену тарифа.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("tariff_upgrade")
        .withProductOfferId("premium_tariff_upgrade")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "tariff_upgrade")
    .withProductOfferId("premium_tariff_upgrade")
    .build()

AppMetrica.report(event: event)
Пользователь подтверждает новый тариф.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("tariff_upgrade", "tariff_confirmation")
        .withProductOfferId("premium_tariff_upgrade")
        .withStepOption("premium_unlimited")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "tariff_upgrade", stepType: "tariff_confirmation")
    .withProductOfferId("premium_tariff_upgrade")
    .withStepOption("premium_unlimited")
    .build()

AppMetrica.report(event: event)
Тариф успешно изменён.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "tariff_upgrade")
        .withProductOfferId("premium_tariff_upgrade")
        .withPayload(mapOf(
            "previous_tariff" to "smart_m",
            "new_tariff" to "premium_unlimited",
            "upgrade_status" to "completed"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "tariff_upgrade", status: .success)
    .withProductOfferId("premium_tariff_upgrade")
    .withPayload([
        "previous_tariff": "smart_m",
        "new_tariff": "premium_unlimited",
        "upgrade_status": "completed"
    ])
    .build()

AppMetrica.report(event: event)

Подключение домашнего интернета

Пользователь открывает раздел домашнего интернета и видит предложение: «Домашний интернет 500 Мбит/с за 990 рублей в месяц».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("home_internet_500", "service")
        .withProductId("home_internet")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("banner")
                    .withIdentifier("home_internet_offer")
                    .withScreen("home_services")
                    .build()
        )
        .withPayload(mapOf(
            "speed_mbps" to "500",
            "monthly_price" to "990",
            "currency" to "RUB",
            "connection_fee" to "0"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "home_internet_500", offerType: "service")
    .withProductId("home_internet")
    .withReferrer(OfferReferrer(type: "banner", identifier: "home_internet_offer", screen: "home_services"))
    .withPayload([
        "speed_mbps": "500",
        "monthly_price": "990",
        "currency": "RUB",
        "connection_fee": "0"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает оформление заявки на подключение домашнего интернета.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("home_internet")
        .withProductOfferId("home_internet_500")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "home_internet")
    .withProductOfferId("home_internet_500")
    .build()

AppMetrica.report(event: event)
Пользователь вводит адрес подключения.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("home_internet", "address_input")
        .withProductOfferId("home_internet_500")
        .withStepOption("manual_input")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "home_internet", stepType: "address_input")
    .withProductOfferId("home_internet_500")
    .withStepOption("manual_input")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает дату визита мастера.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("home_internet", "installation_timeslot")
        .withProductOfferId("home_internet_500")
        .withStepOption("2026-06-20_10:00")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "home_internet", stepType: "installation_timeslot")
    .withProductOfferId("home_internet_500")
    .withStepOption("2026-06-20_10:00")
    .build()

AppMetrica.report(event: event)
Заявка на подключение отправлена, пользователь ждёт визита мастера.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.PENDING, "home_internet")
        .withProductOfferId("home_internet_500")
        .withPayload(mapOf(
            "request_id" to "internet_request_555",
            "connection_status" to "awaiting_installation",
            "selected_timeslot" to "2026-06-20_10:00"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "home_internet", status: .pending)
    .withProductOfferId("home_internet_500")
    .withPayload([
        "request_id": "internet_request_555",
        "connection_status": "awaiting_installation",
        "selected_timeslot": "2026-06-20_10:00"
    ])
    .build()

AppMetrica.report(event: event)
Позже оператор через S2S сообщает, что мастер выполнил подключение, интернет проведён и тариф активирован.
flowResult(
    productId = "home_internet",
    productOfferId = "home_internet_500",
    status = "success",
    payload = mapOf(
        "request_id" to "internet_request_555",
        "installation_status" to "completed",
        "technician_visit" to "completed",
        "tariff_id" to "home_500",
        "tariff_status" to "active",
        "decision_source" to "s2s"
    )
)

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

Travel / Booking

Бронирование отеля по скидочному офферу

Пользователь ищет отель в Белграде и видит карточку: «Скидка 20% на этот отель».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("hotel_discount_may", "booking")
        .withProductId("hotel_booking")
        .withBenefitType("discount_percentage")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("banner")
                    .withIdentifier("search_results_hotel_discount")
                    .withScreen("hotel_search_results")
                    .build()
        )
        .withPayload(mapOf(
            "campaign_id" to "may_hotels_sale",
            "hotel_id" to "hotel_987",
            "city" to "Belgrade",
            "discount_value" to "20",
            "original_price" to "25000",
            "final_price" to "20000",
            "currency" to "RUB",
            "room_type" to "deluxe",
            "refundable" to "true"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "hotel_discount_may", offerType: "booking")
    .withProductId("hotel_booking")
    .withBenefitType("discount_percentage")
    .withReferrer(OfferReferrer(type: "banner", identifier: "search_results_hotel_discount", screen: "hotel_search_results"))
    .withPayload([
        "campaign_id": "may_hotels_sale",
        "hotel_id": "hotel_987",
        "city": "Belgrade",
        "discount_value": "20",
        "original_price": "25000",
        "final_price": "20000",
        "currency": "RUB",
        "room_type": "deluxe",
        "refundable": "true"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает бронирование.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("hotel_booking")
        .withProductOfferId("hotel_discount_may")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "hotel_booking")
    .withProductOfferId("hotel_discount_may")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает даты.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("hotel_booking", "dates")
        .withProductOfferId("hotel_discount_may")
        .withStepOption("3_nights")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "hotel_booking", stepType: "dates")
    .withProductOfferId("hotel_discount_may")
    .withStepOption("3_nights")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает гостей.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("hotel_booking", "guests")
        .withProductOfferId("hotel_discount_may")
        .withStepOption("2_adults")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "hotel_booking", stepType: "guests")
    .withProductOfferId("hotel_discount_may")
    .withStepOption("2_adults")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает способ оплаты.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("hotel_booking", "payment")
        .withProductOfferId("hotel_discount_may")
        .withStepOption("bank_card")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "hotel_booking", stepType: "payment")
    .withProductOfferId("hotel_discount_may")
    .withStepOption("bank_card")
    .build()

AppMetrica.report(event: event)
Бронирование успешно завершено.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "hotel_booking")
        .withProductOfferId("hotel_discount_may")
        .withPayload(mapOf(
            "hotel_id" to "hotel_987",
            "city" to "Belgrade",
            "nights" to "3",
            "guests" to "2",
            "original_price" to "25000",
            "final_price" to "20000",
            "currency" to "RUB",
            "discount_applied" to "true"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "hotel_booking", status: .success)
    .withProductOfferId("hotel_discount_may")
    .withPayload([
        "hotel_id": "hotel_987",
        "city": "Belgrade",
        "nights": "3",
        "guests": "2",
        "original_price": "25000",
        "final_price": "20000",
        "currency": "RUB",
        "discount_applied": "true"
    ])
    .build()

AppMetrica.report(event: event)

Медицинские сервисы

Органическая запись к врачу без оффера

Пользователь сам открывает раздел записи к врачу из меню приложения. Оффера или рекламного предложения не было.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("doctor_appointment")
        .withPayload(mapOf("entry_point" to "menu", "clinic_id" to "clinic_123"))
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "doctor_appointment")
    .withPayload(["entry_point": "menu", "clinic_id": "clinic_123"])
    .build()

AppMetrica.report(event: event)
Пользователь выбирает специализацию врача.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("doctor_appointment", "specialization_selection")
        .withStepOption("therapy")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "doctor_appointment", stepType: "specialization_selection")
    .withStepOption("therapy")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает врача.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("doctor_appointment", "doctor_selection")
        .withStepOption("doctor_456")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "doctor_appointment", stepType: "doctor_selection")
    .withStepOption("doctor_456")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает дату и время приёма.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("doctor_appointment", "timeslot_selection")
        .withStepOption("2026-06-10_14:30")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "doctor_appointment", stepType: "timeslot_selection")
    .withStepOption("2026-06-10_14:30")
    .build()

AppMetrica.report(event: event)
Запись к врачу успешно создана.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "doctor_appointment")
        .withPayload(mapOf(
            "appointment_id" to "appointment_777",
            "clinic_id" to "clinic_123",
            "doctor_specialization" to "therapy"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "doctor_appointment", status: .success)
    .withPayload([
        "appointment_id": "appointment_777",
        "clinic_id": "clinic_123",
        "doctor_specialization": "therapy"
    ])
    .build()

AppMetrica.report(event: event)

Запись к врачу по предложению

Пользователь открывает главный экран приложения клиники и видит карточку: «Запишитесь на чек-ап со скидкой 20%».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("checkup_discount_q2", "service")
        .withProductId("doctor_appointment")
        .withBenefitType("discount_percentage")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("banner")
                    .withIdentifier("main_checkup_banner")
                    .withScreen("main")
                    .build()
        )
        .withPayload(mapOf(
            "clinic_id" to "clinic_123",
            "specialization" to "therapy",
            "discount_value" to "20",
            "campaign_id" to "health_checkup_q2"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "checkup_discount_q2", offerType: "service")
    .withProductId("doctor_appointment")
    .withBenefitType("discount_percentage")
    .withReferrer(OfferReferrer(type: "banner", identifier: "main_checkup_banner", screen: "main"))
    .withPayload([
        "clinic_id": "clinic_123",
        "specialization": "therapy",
        "discount_value": "20",
        "campaign_id": "health_checkup_q2"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает запись к врачу.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("doctor_appointment")
        .withProductOfferId("checkup_discount_q2")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "doctor_appointment")
    .withProductOfferId("checkup_discount_q2")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает врача.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("doctor_appointment", "doctor_selection")
        .withProductOfferId("checkup_discount_q2")
        .withStepOption("therapist")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "doctor_appointment", stepType: "doctor_selection")
    .withProductOfferId("checkup_discount_q2")
    .withStepOption("therapist")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает дату и время приёма.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("doctor_appointment", "timeslot_selection")
        .withProductOfferId("checkup_discount_q2")
        .withStepOption("2026-06-10_14:30")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "doctor_appointment", stepType: "timeslot_selection")
    .withProductOfferId("checkup_discount_q2")
    .withStepOption("2026-06-10_14:30")
    .build()

AppMetrica.report(event: event)
Запись успешно создана.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "doctor_appointment")
        .withProductOfferId("checkup_discount_q2")
        .withPayload(mapOf(
            "appointment_id" to "appointment_555",
            "doctor_specialization" to "therapy",
            "clinic_id" to "clinic_123",
            "discount_applied" to "true"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "doctor_appointment", status: .success)
    .withProductOfferId("checkup_discount_q2")
    .withPayload([
        "appointment_id": "appointment_555",
        "doctor_specialization": "therapy",
        "clinic_id": "clinic_123",
        "discount_applied": "true"
    ])
    .build()

AppMetrica.report(event: event)

Beauty / Wellness

Запись в SPA с предоплатой

Пользователь открывает приложение SPA-салона и видит предложение: «Скидка 20% на SPA-программу при онлайн-оплате».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("spa_weekend_offer", "service")
        .withProductId("spa_booking")
        .withBenefitType("discount_percentage")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("banner")
                    .withIdentifier("spa_weekend_banner")
                    .withScreen("main")
                    .build()
        )
        .withPayload(mapOf(
            "service_type" to "spa_program",
            "discount_value" to "20",
            "original_price" to "12000",
            "final_price" to "9600",
            "currency" to "RUB",
            "campaign_id" to "spa_weekend_q2"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "spa_weekend_offer", offerType: "service")
    .withProductId("spa_booking")
    .withBenefitType("discount_percentage")
    .withReferrer(OfferReferrer(type: "banner", identifier: "spa_weekend_banner", screen: "main"))
    .withPayload([
        "service_type": "spa_program",
        "discount_value": "20",
        "original_price": "12000",
        "final_price": "9600",
        "currency": "RUB",
        "campaign_id": "spa_weekend_q2"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает запись в SPA.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("spa_booking")
        .withProductOfferId("spa_weekend_offer")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "spa_booking")
    .withProductOfferId("spa_weekend_offer")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает SPA-программу.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("spa_booking", "service_selection")
        .withProductOfferId("spa_weekend_offer")
        .withStepOption("relax_spa_program")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "spa_booking", stepType: "service_selection")
    .withProductOfferId("spa_weekend_offer")
    .withStepOption("relax_spa_program")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает дату и время посещения.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("spa_booking", "timeslot_selection")
        .withProductOfferId("spa_weekend_offer")
        .withStepOption("2026-06-15_18:00")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "spa_booking", stepType: "timeslot_selection")
    .withProductOfferId("spa_weekend_offer")
    .withStepOption("2026-06-15_18:00")
    .build()

AppMetrica.report(event: event)
Пользователь переходит к оплате предоплаты.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("spa_booking", "prepayment")
        .withProductOfferId("spa_weekend_offer")
        .withStepOption("bank_card")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "spa_booking", stepType: "prepayment")
    .withProductOfferId("spa_weekend_offer")
    .withStepOption("bank_card")
    .build()

AppMetrica.report(event: event)
Предоплата успешно внесена, запись подтверждена.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "spa_booking")
        .withProductOfferId("spa_weekend_offer")
        .withPayload(mapOf(
            "booking_id" to "spa_booking_555",
            "service_type" to "relax_spa_program",
            "prepayment_amount" to "3000",
            "remaining_amount" to "6600",
            "currency" to "RUB",
            "payment_status" to "partially_paid"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "spa_booking", status: .success)
    .withProductOfferId("spa_weekend_offer")
    .withPayload([
        "booking_id": "spa_booking_555",
        "service_type": "relax_spa_program",
        "prepayment_amount": "3000",
        "remaining_amount": "6600",
        "currency": "RUB",
        "payment_status": "partially_paid"
    ])
    .build()

AppMetrica.report(event: event)
Позже SPA-салон через S2S подтверждает, что пользователь действительно посетил процедуру.
flowResult(
    productId = "spa_booking",
    productOfferId = "spa_weekend_offer",
    status = "success",
    payload = mapOf(
        "booking_id" to "spa_booking_555",
        "visit_status" to "completed",
        "service_type" to "relax_spa_program",
        "final_payment_amount" to "6600",
        "currency" to "RUB",
        "decision_source" to "s2s"
    )
)

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

Запись на маникюр

Пользователь открывает приложение салона и видит предложение: «Скидка 15% на первое посещение».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("first_visit_nails", "service")
        .withProductId("beauty_appointment")
        .withBenefitType("discount_percentage")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("popup")
                    .withIdentifier("new_user_discount_popup")
                    .withScreen("main")
                    .build()
        )
        .withPayload(mapOf(
            "service_type" to "manicure",
            "discount_value" to "15",
            "campaign_id" to "new_clients_q2"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "first_visit_nails", offerType: "service")
    .withProductId("beauty_appointment")
    .withBenefitType("discount_percentage")
    .withReferrer(OfferReferrer(type: "popup", identifier: "new_user_discount_popup", screen: "main"))
    .withPayload([
        "service_type": "manicure",
        "discount_value": "15",
        "campaign_id": "new_clients_q2"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает запись в салон.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("beauty_appointment")
        .withProductOfferId("first_visit_nails")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "beauty_appointment")
    .withProductOfferId("first_visit_nails")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает услугу.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("beauty_appointment", "service_selection")
        .withProductOfferId("first_visit_nails")
        .withStepOption("manicure_gel")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "beauty_appointment", stepType: "service_selection")
    .withProductOfferId("first_visit_nails")
    .withStepOption("manicure_gel")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает мастера.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("beauty_appointment", "master_selection")
        .withProductOfferId("first_visit_nails")
        .withStepOption("top_master")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "beauty_appointment", stepType: "master_selection")
    .withProductOfferId("first_visit_nails")
    .withStepOption("top_master")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает дату и время.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("beauty_appointment", "timeslot_selection")
        .withProductOfferId("first_visit_nails")
        .withStepOption("2026-06-12_18:00")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "beauty_appointment", stepType: "timeslot_selection")
    .withProductOfferId("first_visit_nails")
    .withStepOption("2026-06-12_18:00")
    .build()

AppMetrica.report(event: event)
Запись успешно создана.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "beauty_appointment")
        .withProductOfferId("first_visit_nails")
        .withPayload(mapOf(
            "appointment_id" to "beauty_booking_987",
            "service_type" to "manicure_gel",
            "master_level" to "top_master",
            "discount_applied" to "true"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "beauty_appointment", status: .success)
    .withProductOfferId("first_visit_nails")
    .withPayload([
        "appointment_id": "beauty_booking_987",
        "service_type": "manicure_gel",
        "master_level": "top_master",
        "discount_applied": "true"
    ])
    .build()

AppMetrica.report(event: event)

Запись на стрижку и окрашивание с последующей отменой

Пользователь открывает приложение салона и самостоятельно начинает запись на стрижку и окрашивание. Оффера или рекламного предложения не было.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("beauty_appointment")
        .withPayload(mapOf("entry_point" to "catalog", "service_category" to "hair"))
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "beauty_appointment")
    .withPayload(["entry_point": "catalog", "service_category": "hair"])
    .build()

AppMetrica.report(event: event)
Пользователь выбирает услугу: стрижка и окрашивание.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("beauty_appointment", "service_selection")
        .withStepOption("haircut_coloring")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "beauty_appointment", stepType: "service_selection")
    .withStepOption("haircut_coloring")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает мастера.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("beauty_appointment", "master_selection")
        .withStepOption("top_master")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "beauty_appointment", stepType: "master_selection")
    .withStepOption("top_master")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает дату и время записи.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("beauty_appointment", "timeslot_selection")
        .withStepOption("2026-06-20_15:00")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "beauty_appointment", stepType: "timeslot_selection")
    .withStepOption("2026-06-20_15:00")
    .build()

AppMetrica.report(event: event)
Запись успешно создана.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "beauty_appointment")
        .withPayload(mapOf(
            "booking_id" to "beauty_booking_777",
            "service_type" to "haircut_coloring",
            "master_level" to "top_master",
            "booking_status" to "confirmed"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "beauty_appointment", status: .success)
    .withPayload([
        "booking_id": "beauty_booking_777",
        "service_type": "haircut_coloring",
        "master_level": "top_master",
        "booking_status": "confirmed"
    ])
    .build()

AppMetrica.report(event: event)
Позже пользователь отменяет запись через приложение.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.CANCELLED, "beauty_appointment")
        .withPayload(mapOf(
            "booking_id" to "beauty_booking_777",
            "cancellation_reason" to "client_cancelled",
            "cancellation_source" to "app"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "beauty_appointment", status: .cancelled)
    .withPayload([
        "booking_id": "beauty_booking_777",
        "cancellation_reason": "client_cancelled",
        "cancellation_source": "app"
    ])
    .build()

AppMetrica.report(event: event)
Или салон через S2S сообщает, что запись была отменена.
flowResult(
    productId = "beauty_appointment",
    productOfferId = null,
    status = "cancelled",
    payload = mapOf(
        "booking_id" to "beauty_booking_777",
        "cancellation_reason" to "client_cancelled",
        "decision_source" to "s2s"
    )
)

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

Subscription / Media

Trial-подписка без отдельного flowStart

Пользователь получает push-уведомление: «Попробуйте Premium бесплатно 30 дней».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("premium_trial_30d", "subscription")
        .withProductId("premium_subscription")
        .withBenefitType("trial")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("notification")
                    .withIdentifier("premium_trial_push")
                    .build()
        )
        .withPayload(mapOf(
            "trial_days" to "30",
            "subscription_plan" to "premium_monthly",
            "campaign_id" to "trial_reactivation_2026"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "premium_trial_30d", offerType: "subscription")
    .withProductId("premium_subscription")
    .withBenefitType("trial")
    .withReferrer(OfferReferrer(type: "notification", identifier: "premium_trial_push"))
    .withPayload([
        "trial_days": "30",
        "subscription_plan": "premium_monthly",
        "campaign_id": "trial_reactivation_2026"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь нажимает на push и сразу активирует trial. Отдельного процесса оформления нет, поэтому flowStart можно не отправлять.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "premium_subscription")
        .withProductOfferId("premium_trial_30d")
        .withPayload(mapOf(
            "trial_days" to "30",
            "subscription_plan" to "premium_monthly",
            "activation_source" to "push"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "premium_subscription", status: .success)
    .withProductOfferId("premium_trial_30d")
    .withPayload([
        "trial_days": "30",
        "subscription_plan": "premium_monthly",
        "activation_source": "push"
    ])
    .build()

AppMetrica.report(event: event)

Видео-платформы и онлайн-кинотеатры

Активация trial-подписки

Пользователь открывает приложение онлайн-кинотеатра и видит fullscreen paywall: «30 дней Premium бесплатно».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("premium_trial_30d", "subscription")
        .withProductId("premium_subscription")
        .withBenefitType("trial")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("screen")
                    .withIdentifier("premium_paywall")
                    .withScreen("home")
                    .build()
        )
        .withPayload(mapOf(
            "trial_days" to "30",
            "subscription_plan" to "premium_monthly",
            "campaign_id" to "summer_trial_2026"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "premium_trial_30d", offerType: "subscription")
    .withProductId("premium_subscription")
    .withBenefitType("trial")
    .withReferrer(OfferReferrer(type: "screen", identifier: "premium_paywall", screen: "home"))
    .withPayload([
        "trial_days": "30",
        "subscription_plan": "premium_monthly",
        "campaign_id": "summer_trial_2026"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь нажимает «Попробовать бесплатно».
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("premium_subscription")
        .withProductOfferId("premium_trial_30d")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "premium_subscription")
    .withProductOfferId("premium_trial_30d")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает тариф.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("premium_subscription", "plan_selection")
        .withProductOfferId("premium_trial_30d")
        .withStepOption("premium_monthly")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "premium_subscription", stepType: "plan_selection")
    .withProductOfferId("premium_trial_30d")
    .withStepOption("premium_monthly")
    .build()

AppMetrica.report(event: event)
Пользователь добавляет способ оплаты.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("premium_subscription", "payment_method")
        .withProductOfferId("premium_trial_30d")
        .withStepOption("bank_card")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "premium_subscription", stepType: "payment_method")
    .withProductOfferId("premium_trial_30d")
    .withStepOption("bank_card")
    .build()

AppMetrica.report(event: event)
Пробная подписка успешно активирована.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "premium_subscription")
        .withProductOfferId("premium_trial_30d")
        .withPayload(mapOf(
            "subscription_plan" to "premium_monthly",
            "trial_days" to "30",
            "autorenew_enabled" to "true"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "premium_subscription", status: .success)
    .withProductOfferId("premium_trial_30d")
    .withPayload([
        "subscription_plan": "premium_monthly",
        "trial_days": "30",
        "autorenew_enabled": "true"
    ])
    .build()

AppMetrica.report(event: event)

Аренда фильма

Пользователь открывает карточку фильма и видит предложение: «Аренда фильма за 299 руб.».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("movie_rental_offer", "digital_good")
        .withProductId("movie_rental")
        .withBenefitType("discount_absolute")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("button")
                    .withIdentifier("rent_movie_button")
                    .withScreen("movie_details")
                    .build()
        )
        .withPayload(mapOf(
            "movie_id" to "movie_987",
            "movie_title" to "SciFi Adventure",
            "rental_price" to "299",
            "currency" to "RUB"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "movie_rental_offer", offerType: "digital_good")
    .withProductId("movie_rental")
    .withBenefitType("discount_absolute")
    .withReferrer(OfferReferrer(type: "button", identifier: "rent_movie_button", screen: "movie_details"))
    .withPayload([
        "movie_id": "movie_987",
        "movie_title": "SciFi Adventure",
        "rental_price": "299",
        "currency": "RUB"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает оформление аренды.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("movie_rental")
        .withProductOfferId("movie_rental_offer")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "movie_rental")
    .withProductOfferId("movie_rental_offer")
    .build()

AppMetrica.report(event: event)
Пользователь подтверждает оплату.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("movie_rental", "payment_confirmation")
        .withProductOfferId("movie_rental_offer")
        .withStepOption("bank_card")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "movie_rental", stepType: "payment_confirmation")
    .withProductOfferId("movie_rental_offer")
    .withStepOption("bank_card")
    .build()

AppMetrica.report(event: event)
Аренда фильма успешно оформлена.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "movie_rental")
        .withProductOfferId("movie_rental_offer")
        .withPayload(mapOf(
            "movie_id" to "movie_987",
            "rental_duration_hours" to "48",
            "payment_status" to "paid"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "movie_rental", status: .success)
    .withProductOfferId("movie_rental_offer")
    .withPayload([
        "movie_id": "movie_987",
        "rental_duration_hours": "48",
        "payment_status": "paid"
    ])
    .build()

AppMetrica.report(event: event)

Покупка билета на онлайн-показ фильма

Пользователь открывает карточку фильма и видит предложение: «Купить билет на онлайн-показ за 500 рублей».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("online_premiere_ticket_offer", "digital_good")
        .withProductId("online_event_ticket")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("button")
                    .withIdentifier("buy_ticket_button")
                    .withScreen("premiere_details")
                    .build()
        )
        .withPayload(mapOf(
            "event_id" to "premiere_987",
            "event_type" to "online_premiere",
            "content_id" to "movie_987",
            "ticket_price" to "500",
            "currency" to "RUB"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "online_premiere_ticket_offer", offerType: "digital_good")
    .withProductId("online_event_ticket")
    .withReferrer(OfferReferrer(type: "button", identifier: "buy_ticket_button", screen: "premiere_details"))
    .withPayload([
        "event_id": "premiere_987",
        "event_type": "online_premiere",
        "content_id": "movie_987",
        "ticket_price": "500",
        "currency": "RUB"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает покупку билета.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("online_event_ticket")
        .withProductOfferId("online_premiere_ticket_offer")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "online_event_ticket")
    .withProductOfferId("online_premiere_ticket_offer")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает тип билета.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("online_event_ticket", "ticket_type_selection")
        .withProductOfferId("online_premiere_ticket_offer")
        .withStepOption("standard")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "online_event_ticket", stepType: "ticket_type_selection")
    .withProductOfferId("online_premiere_ticket_offer")
    .withStepOption("standard")
    .build()

AppMetrica.report(event: event)
Пользователь подтверждает оплату.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("online_event_ticket", "payment_confirmation")
        .withProductOfferId("online_premiere_ticket_offer")
        .withStepOption("bank_card")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "online_event_ticket", stepType: "payment_confirmation")
    .withProductOfferId("online_premiere_ticket_offer")
    .withStepOption("bank_card")
    .build()

AppMetrica.report(event: event)
Билет успешно куплен.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "online_event_ticket")
        .withProductOfferId("online_premiere_ticket_offer")
        .withPayload(mapOf(
            "event_id" to "premiere_987",
            "content_id" to "movie_987",
            "ticket_type" to "standard",
            "payment_status" to "paid"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "online_event_ticket", status: .success)
    .withProductOfferId("online_premiere_ticket_offer")
    .withPayload([
        "event_id": "premiere_987",
        "content_id": "movie_987",
        "ticket_type": "standard",
        "payment_status": "paid"
    ])
    .build()

AppMetrica.report(event: event)

Loyalty / Promo

Получение промокода без сложного оформления

Пользователь видит popup на экране checkout: «Получите промокод SUMMER20».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("promo_summer_20", "digital_good")
        .withProductId("promo_code")
        .withBenefitType("discount_absolute")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("popup")
                    .withIdentifier("summer_promo_popup")
                    .withScreen("checkout")
                    .build()
        )
        .withPayload(mapOf(
            "promo_code" to "SUMMER20",
            "discount_value" to "20",
            "currency" to "EUR",
            "min_order_value" to "100"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "promo_summer_20", offerType: "digital_good")
    .withProductId("promo_code")
    .withBenefitType("discount_absolute")
    .withReferrer(OfferReferrer(type: "popup", identifier: "summer_promo_popup", screen: "checkout"))
    .withPayload([
        "promo_code": "SUMMER20",
        "discount_value": "20",
        "currency": "EUR",
        "min_order_value": "100"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь нажимает «Получить промокод». Отдельного flow нет, поэтому можно сразу отправить результат.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "promo_code")
        .withProductOfferId("promo_summer_20")
        .withPayload(mapOf("promo_code" to "SUMMER20", "copied_to_clipboard" to "true"))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "promo_code", status: .success)
    .withProductOfferId("promo_summer_20")
    .withPayload(["promo_code": "SUMMER20", "copied_to_clipboard": "true"])
    .build()

AppMetrica.report(event: event)

Страхование

Оформление страховки с серверным решением (S2S result)

Пользователь открывает экран поездки и видит кнопку: «Добавить страховку со скидкой 15%».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("travel_insurance_offer", "insurance")
        .withProductId("travel_insurance")
        .withBenefitType("discount_percentage")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("button")
                    .withIdentifier("trip_details_insurance_button")
                    .withScreen("trip_details")
                    .build()
        )
        .withPayload(mapOf(
            "campaign_id" to "travel_insurance_q2",
            "discount_value" to "15",
            "destination_country" to "Serbia",
            "trip_duration_days" to "7"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "travel_insurance_offer", offerType: "insurance")
    .withProductId("travel_insurance")
    .withBenefitType("discount_percentage")
    .withReferrer(OfferReferrer(type: "button", identifier: "trip_details_insurance_button", screen: "trip_details"))
    .withPayload([
        "campaign_id": "travel_insurance_q2",
        "discount_value": "15",
        "destination_country": "Serbia",
        "trip_duration_days": "7"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь начинает оформление страховки.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("travel_insurance")
        .withProductOfferId("travel_insurance_offer")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "travel_insurance")
    .withProductOfferId("travel_insurance_offer")
    .build()

AppMetrica.report(event: event)
Пользователь выбирает покрытие.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("travel_insurance", "coverage_selection")
        .withProductOfferId("travel_insurance_offer")
        .withStepOption("standard")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "travel_insurance", stepType: "coverage_selection")
    .withProductOfferId("travel_insurance_offer")
    .withStepOption("standard")
    .build()

AppMetrica.report(event: event)
Пользователь вводит данные.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("travel_insurance", "traveler_details")
        .withProductOfferId("travel_insurance_offer")
        .withStepOption("manual_input")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "travel_insurance", stepType: "traveler_details")
    .withProductOfferId("travel_insurance_offer")
    .withStepOption("manual_input")
    .build()

AppMetrica.report(event: event)
Заявка отправлена на проверку.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.PENDING, "travel_insurance")
        .withProductOfferId("travel_insurance_offer")
        .withPayload(mapOf(
            "application_id" to "insurance_app_987",
            "coverage_type" to "standard",
            "destination_country" to "Serbia"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "travel_insurance", status: .pending)
    .withProductOfferId("travel_insurance_offer")
    .withPayload([
        "application_id": "insurance_app_987",
        "coverage_type": "standard",
        "destination_country": "Serbia"
    ])
    .build()

AppMetrica.report(event: event)
Позже страховая присылает финальный результат через S2S.
flowResult(
    productId = "travel_insurance",
    productOfferId = "travel_insurance_offer",
    status = "success",
    payload = mapOf(
        "application_id" to "insurance_app_987",
        "policy_id" to "policy_555",
        "decision_source" to "s2s"
    )
)

Gaming

Покупка игрового bundle

Пользователь открывает игровой магазин и видит баннер: «Starter bundle: монеты + гемы + бонусный предмет».
AppMetrica.reportEvent(
    ProductFlowEvent.offerShown("starter_bundle_offer", "bundle")
        .withProductId("starter_bundle")
        .withBenefitType("bundle_extra")
        .withReferrer(
                OfferReferrer.newBuilder()
                    .withType("banner")
                    .withIdentifier("store_starter_bundle_banner")
                    .withScreen("game_store")
                    .build()
        )
        .withPayload(mapOf(
            "bundle_id" to "starter_bundle",
            "bonus_items" to "coins,gems,sword_skin",
            "price_tier" to "low",
            "campaign_id" to "starter_pack_campaign"
        ))
        .build()
)
let event = ProductFlowEvents.offerShown(productOfferId: "starter_bundle_offer", offerType: "bundle")
    .withProductId("starter_bundle")
    .withBenefitType("bundle_extra")
    .withReferrer(OfferReferrer(type: "banner", identifier: "store_starter_bundle_banner", screen: "game_store"))
    .withPayload([
        "bundle_id": "starter_bundle",
        "bonus_items": "coins",
        "price_tier": "low",
        "campaign_id": "starter_pack_campaign"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь открывает покупку bundle.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("starter_bundle")
        .withProductOfferId("starter_bundle_offer")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "starter_bundle")
    .withProductOfferId("starter_bundle_offer")
    .build()

AppMetrica.report(event: event)
Пользователь подтверждает покупку.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("starter_bundle", "purchase_confirmation")
        .withProductOfferId("starter_bundle_offer")
        .withStepOption("confirmed")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "starter_bundle", stepType: "purchase_confirmation")
    .withProductOfferId("starter_bundle_offer")
    .withStepOption("confirmed")
    .build()

AppMetrica.report(event: event)
Покупка успешно завершена.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "starter_bundle")
        .withProductOfferId("starter_bundle_offer")
        .withPayload(mapOf(
            "bundle_id" to "starter_bundle",
            "payment_status" to "paid",
            "bonus_items_granted" to "true"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "starter_bundle", status: .success)
    .withProductOfferId("starter_bundle_offer")
    .withPayload([
        "bundle_id": "starter_bundle",
        "payment_status": "paid",
        "bonus_items_granted": "true"
    ])
    .build()

AppMetrica.report(event: event)

Опросы и оценка товара

Одностраничный опрос после покупки

Пользователь завершил покупку и видит экран: «Оцените товар». Оффера нет, flowStart можно не отправлять.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "product_review")
        .withPayload(mapOf(
            "survey_id" to "post_purchase_review",
            "product_id" to "sku_12345",
            "rating" to "5",
            "has_comment" to "true"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "product_review", status: .success)
    .withPayload([
        "survey_id": "post_purchase_review",
        "product_id": "sku_12345",
        "rating": "5",
        "has_comment": "true"
    ])
    .build()

AppMetrica.report(event: event)

Многошаговый опрос после покупки

Пользователь завершил покупку и видит экран: «Помогите нам улучшить рекомендации — ответьте на несколько вопросов». Оффера нет, но есть многошаговый flow.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("product_review_survey")
        .withPayload(mapOf(
            "survey_id" to "post_purchase_review",
            "product_id" to "sku_12345",
            "entry_point" to "order_success_screen"
        ))
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "product_review_survey")
    .withPayload([
        "survey_id": "post_purchase_review",
        "product_id": "sku_12345",
        "entry_point": "order_success_screen"
    ])
    .build()

AppMetrica.report(event: event)
Пользователь ставит оценку товару.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("product_review_survey", "rating")
        .withStepOption("5")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "product_review_survey", stepType: "rating")
    .withStepOption("5")
    .build()

AppMetrica.report(event: event)
Пользователь отвечает, что понравилось.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("product_review_survey", "positive_feedback")
        .withStepOption("quality")
        .build()
)
let event = ProductFlowEvents.flowStep(productId: "product_review_survey", stepType: "positive_feedback")
    .withStepOption("quality")
    .build()

AppMetrica.report(event: event)
Пользователь переходит к шагу комментария, но закрывает экран. Последний flowStep перед flowResult(cancelled) поможет понять, где произошёл отказ.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStepForProduct("product_review_survey", "comment")
        .withStepOption("optional_text")
        .build()
)
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.CANCELLED, "product_review_survey")
        .withPayload(mapOf(
            "survey_id" to "post_purchase_review",
            "product_id" to "sku_12345",
            "last_completed_step" to "comment"
        ))
        .build()
)
let commentStep = ProductFlowEvents.flowStep(productId: "product_review_survey", stepType: "comment")
    .withStepOption("optional_text")
    .build()

AppMetrica.report(event: commentStep)
let result = ProductFlowEvents.flowResult(productId: "product_review_survey", status: .cancelled)
    .withPayload([
        "survey_id": "post_purchase_review",
        "product_id": "sku_12345",
        "last_completed_step": "comment"
    ])
    .build()

AppMetrica.report(event: result)
Пользователь прошёл опрос до конца.
AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "product_review_survey")
        .withPayload(mapOf(
            "survey_id" to "post_purchase_review",
            "product_id" to "sku_12345",
            "rating" to "5",
            "positive_feedback" to "quality",
            "has_comment" to "true"
        ))
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "product_review_survey", status: .success)
    .withPayload([
        "survey_id": "post_purchase_review",
        "product_id": "sku_12345",
        "rating": "5",
        "positive_feedback": "quality",
        "has_comment": "true"
    ])
    .build()

AppMetrica.report(event: event)

Как читать сценарии разметки

offerShown
Отправляется, если пользователь действительно увидел предложение: баннер, popup, push-уведомление, кнопку, карточку или другой промо-блок.
flowStart
Отправляется, если у пользователя есть отдельный старт оформления, активации или многошагового сценария.
flowStep
Отправляется для важных промежуточных шагов. Особенно полезен, если нужно определить, на каком этапе пользователи останавливаются или отказываются от сценария.
flowResult
Отправляется всегда, когда известен итог сценария.

Если результат известен не сразу, сначала отправляется промежуточный результат:

AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.PENDING, "...")
        .withProductOfferId("...")
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "...", status: .pending)
    .withProductOfferId("...")
    .build()

AppMetrica.report(event: event)

А позже через S2S отправляется финальный результат:

AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.SUCCESS, "...")
        .withProductOfferId("...")
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "...", status: .success)
    .withProductOfferId("...")
    .build()

AppMetrica.report(event: event)

или

AppMetrica.reportEvent(
    ProductFlowEvent.flowResultForProduct(ProductFlowStatus.DECLINED, "...")
        .withProductOfferId("...")
        .build()
)
let event = ProductFlowEvents.flowResult(productId: "...", status: .declined)
    .withProductOfferId("...")
    .build()

AppMetrica.report(event: event)

Статус

Значение

success

Успешное завершение

declined

Отклонено третьей стороной

pending

Ожидает решения

cancelled

Пользователь отменил

expired

Оффер или сессия истекли

fail

Техническая ошибка

Как понять, подходит ли вам новая разметка событий

Рекомендуем данную схему разметки, если в вашем приложении есть:

  • офферы;
  • карточки предложений;
  • onboarding-сценарии;
  • сценарии оформления заказа;
  • сценарии оформления финансовых продуктов;
  • сценарии бронирования;
  • сценарии оформления подписки;
  • сценарии апгрейда тарифа или продукта;
  • сценарии одобрения;
  • аналитика воронок.

Схема будет особенно полезна, если в текущей реализации:

  • все события отправляются как custom events;
  • события трудно сопоставлять друг с другом;
  • одинаковые шаги называются по-разному;
  • сложно анализировать конверсию между этапами;
  • нет единого подхода к продуктовой воронке.

Переход с custom events

Если вы уже используете custom events, переход может быть постепенным: новые события можно отправлять параллельно с существующими кастомными событиями.

Новые типовые события не расходуют лимит custom events и позволяют проверить корректность интеграции и полноту данных перед миграцией.

После проверки отправку соответствующих custom events можно отключить.

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

Написать в службу поддержки Предложить улучшение для документации