Architecture of Product Scenarios and Offer Events

Documentation on Product Flow with SDK integration examples:

More about the approach

Product Flow is a new schema for handling standard AppMetrica events in apps with offers, purchases, applications, and multi-step product and service ordering scenarios.

This approach helps standardize analytics for the following entities and scenarios:

  • product offers;
  • marketing offers;
  • purchases of goods and services;
  • user funnels;
  • ordering steps;
  • applications and approval results;
  • asynchronous S2S results.

We recommend using the Product Flow approach for the following types of apps:

  • banks;
  • insurance services;
  • telecom apps;
  • booking and travel services;
  • subscription services;
  • delivery services;
  • marketplaces;
  • gaming apps;
  • any apps with product scenarios, purchases, and funnel analytics.

Does not consume custom events and DSA limits

The new standard events do not count toward the following limits:

  • custom events;
  • Data Stream API.

This allows you to:

  • gradually adopt the new markup schema;
  • send more product scenarios and steps;
  • build detailed funnels and analytics without consuming custom event and DSA limits.

What problem does this approach solve

Currently, many apps use custom events to send analytics data about the following entities and scenarios:

  • offers;
  • applications;
  • product scenarios;
  • onboarding scenarios;
  • order placement scenarios;
  • approval scenarios.

This approach leads to the following problems:

  • identical scenarios are marked up differently;
  • building standard reports is difficult;
  • analytics is hard to transfer between teams;
  • automatic funnel building is impossible;
  • it is difficult to compare the effectiveness of offers and scenarios.

The product scenario and offer event schema offers a unified approach to marking up key stages:

  • offer display;
  • start of ordering;
  • intermediate steps;
  • final result.

Main events

The interaction schema is based on four main events:

Event

Meaning

offerShown

The user saw an offer or proposal

flowStart

The user started ordering a product

flowStep

An intermediate ordering step

flowResult

The final result

Markup scenarios

This section shows complete user scenarios for different industries.

Scenario examples

All scenarios in this document are examples of markup and demonstrate possible integration options.

Event names, parameters, scenario steps, and payload structure can be adapted to the specifics of your app and user scenarios.

Each scenario describes:

  • what the user sees and does in the app;
  • which events need to be sent;
  • when offerShown can be omitted;
  • when flowStart can be omitted;
  • how to pass intermediate steps via flowStep;
  • how to pass the final result via flowResult;
  • how to use payload, referrer, and stepOption;
  • how to pass the result later via S2S.

Banks and Fintech

Loan via personal offer: offer → application → pending → S2S result

The user sees a banner: 'You are pre-approved for a loan of up to 500,000 RUB at a rate from 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)
The user taps the banner and starts filling out the application.
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)
The user uploads documents.
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)
The user submits the application for scoring.
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)
The user has completed their part, the application is under review.
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)
Later, the bank sends the final result via 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"
    )
)

Sending offer events via the S2S scheme will be implemented via the POST API in one of the upcoming releases. The current documentation provides an example script that demonstrates the expected integration method.

Organic loan application without an offer

The user opened the 'Loans' section from the app menu on their own. There was no offer.
AppMetrica.reportEvent(
    ProductFlowEvent.flowStart("personal_loan")
        .build()
)
let event = ProductFlowEvents.flowStart(productId: "personal_loan")
    .build()

AppMetrica.report(event: event)
The user uploads documents.
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)
The application is submitted for review.
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)
Later, the bank sends a rejection via 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"
    )
)

Sending offer events via the S2S scheme will be implemented via the POST API in one of the upcoming releases. The current documentation provides an example script that demonstrates the expected integration method.

Telecom

eSIM activation via offer

The user opens the tariff screen and sees a banner: 'eSIM with 20% off the first month'.
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)
The user taps the banner and starts activation.
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)
The user selects a tariff.
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)
The user goes through identity verification.
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 successfully activated.
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)

Roaming package activation before a trip

The user opens the roaming section and sees an offer: '10 GB in Europe for 14 days for 1,500 RUB'.
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)
The user starts activating the roaming package.
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)
The user confirms the activation terms.
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)
Roaming package successfully activated.
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)

Tariff upgrade

The user sees an offer: 'Upgrade to the Premium tariff with unlimited internet'.
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)
The user starts changing the tariff.
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)
The user confirms the new tariff.
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)
Tariff successfully changed.
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)

Home internet connection

The user opens the home internet section and sees an offer: 'Home internet 500 Mbps for 990 RUB per month'.
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)
The user starts filling out the home internet connection application.
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)
The user enters the connection address.
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)
The user selects the technician visit date.
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)
The connection request has been submitted, the user is waiting for the technician visit.
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)
Later, the operator reports via S2S that the technician has completed the installation, the internet is connected, and the tariff is activated.
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"
    )
)

Sending offer events via the S2S scheme will be implemented via the POST API in one of the upcoming releases. The current documentation provides an example script that demonstrates the expected integration method.

Travel / Booking

Hotel booking with a discount offer

The user is searching for a hotel in Belgrade and sees a card: '20% off this hotel'.
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)
The user starts booking.
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)
The user selects dates.
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)
The user selects guests.
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)
The user selects a payment method.
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)
Booking successfully completed.
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)

Medical services

Organic doctor appointment without an offer

The user opens the doctor appointment section from the app menu on their own. There was no offer or promotional proposal.
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)
The user selects a doctor's specialization.
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)
The user selects a doctor.
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)
The user selects the appointment date and time.
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)
Doctor appointment successfully created.
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)

Doctor appointment via offer

The user opens the clinic app's main screen and sees a card: 'Book a checkup with 20% off'.
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)
The user starts booking a doctor appointment.
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)
The user selects a doctor.
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)
The user selects the appointment date and time.
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)
Appointment successfully created.
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 booking with prepayment

The user opens the SPA salon app and sees an offer: '20% off SPA program with online payment'.
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)
The user starts booking the 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)
The user selects the SPA program.
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)
The user selects the visit date and time.
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)
The user proceeds to prepayment.
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)
Prepayment successfully made, booking confirmed.
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)
Later, the SPA salon confirms via S2S that the user actually attended the treatment.
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"
    )
)

Sending offer events via the S2S scheme will be implemented via the POST API in one of the upcoming releases. The current documentation provides an example script that demonstrates the expected integration method.

Manicure appointment

The user opens the salon app and sees an offer: '15% off your first visit'.
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)
The user starts booking at the salon.
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)
The user selects a service.
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)
The user selects a master.
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)
The user selects date and time.
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)
Appointment successfully created.
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)

Haircut and coloring appointment with subsequent cancellation

The user opens the salon app and independently starts booking a haircut and coloring. There was no offer or promotional proposal.
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)
The user selects a service: haircut and coloring.
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)
The user selects a master.
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)
The user selects the appointment date and time.
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)
Appointment successfully created.
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)
Later, the user cancels the appointment through the app.
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)
Or the salon reports via S2S that the appointment was cancelled.
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"
    )
)

Sending offer events via the S2S scheme will be implemented via the POST API in one of the upcoming releases. The current documentation provides an example script that demonstrates the expected integration method.

Subscription / Media

Trial subscription without a separate flowStart

The user receives a push notification: 'Try Premium free for 30 days'.
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)
The user taps the push and immediately activates the trial. There is no separate ordering process, so flowStart can be omitted.
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)

Video platforms and online cinemas

Trial subscription activation

The user opens the online cinema app and sees a fullscreen paywall: '30 days Premium for free'.
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)
The user taps 'Try for free'.
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)
The user selects a plan.
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)
The user adds a payment method.
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)
Trial subscription successfully activated.
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)

Movie rental

The user opens a movie card and sees an offer: 'Rent this movie for 299 RUB'.
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)
The user starts the rental process.
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)
The user confirms payment.
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)
Movie rental successfully completed.
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)

Buying a ticket for an online movie screening

The user opens a movie card and sees an offer: 'Buy a ticket for the online screening for 500 RUB'.
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)
The user starts buying the ticket.
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)
The user selects the ticket type.
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)
The user confirms payment.
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)
Ticket successfully purchased.
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

Getting a promo code without a complex flow

The user sees a popup on the checkout screen: 'Get promo code 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)
The user taps 'Get promo code'. There is no separate flow, so the result can be sent immediately.
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)

Insurance

Insurance application with server-side decision (S2S result)

The user opens the trip screen and sees a button: 'Add insurance with 15% off'.
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)
The user starts the insurance application.
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)
The user selects coverage.
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)
The user enters their details.
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)
The application has been submitted for review.
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)
Later, the insurance company sends the final result via 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"
    )
)

Sending offer events via the S2S scheme will be implemented via the POST API in one of the upcoming releases. The current documentation provides an example script that demonstrates the expected integration method.

Gaming

Purchasing a game bundle

The user opens the in-game store and sees a banner: 'Starter bundle: coins + gems + bonus item'.
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)
The user opens the bundle purchase.
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)
The user confirms the purchase.
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)
Purchase successfully completed.
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)

Surveys and product reviews

One-page post-purchase survey

The user has completed a purchase and sees a screen: 'Rate the product'. There is no offer, flowStart can be omitted.
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)

Multi-step post-purchase survey

The user has completed a purchase and sees a screen: 'Help us improve recommendations — answer a few questions'. There is no offer, but there is a multi-step 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)
The user rates the product.
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)
The user answers what they liked.
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)
The user proceeds to the comment step but closes the screen. The last flowStep before flowResult(cancelled) helps identify where the drop-off occurred.
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)
The user completed the survey to the end.
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)

How to read the markup scenarios

offerShown
Sent if the user actually saw the offer: a banner, popup, push notification, button, card, or other promo block.
flowStart
Sent if the user has a separate start for ordering, activation, or a multi-step scenario.
flowStep
Sent for important intermediate steps. Especially useful when you need to determine at which stage users stop or abandon the scenario.
flowResult
Always sent when the scenario outcome is known.

If the result is not known immediately, an intermediate result is sent first:

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

AppMetrica.report(event: event)

And later, the final result is sent via S2S:

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

AppMetrica.report(event: event)

or

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

AppMetrica.report(event: event)

Status

Meaning

success

Successful completion

declined

Declined by a third party

pending

Awaiting decision

cancelled

Cancelled by the user

expired

Offer or session expired

fail

Technical error

How to determine if the new event markup is right for you

We recommend this markup schema if your app has:

  • offers;
  • proposal cards;
  • onboarding scenarios;
  • order placement scenarios;
  • financial product application scenarios;
  • booking scenarios;
  • subscription scenarios;
  • tariff or product upgrade scenarios;
  • approval scenarios;
  • funnel analytics.

The schema will be especially useful if in the current implementation:

  • all events are sent as custom events;
  • events are difficult to correlate with each other;
  • identical steps are named differently;
  • it is difficult to analyze conversion between stages;
  • there is no unified approach to the product funnel.

Migrating from custom events

If you are already using custom events, the migration can be gradual: new events can be sent in parallel with existing custom events.

The new standard events do not consume the custom events limit and allow you to verify the correctness of the integration and data completeness before migration.

After verification, sending the corresponding custom events can be disabled.

If you didn't find the answer you were looking for, you can use the feedback form to submit your question. Please describe the problem in as much detail as possible. Attach a screenshot if possible.

Contact support Suggest an improvement for documentation