Architecture of Product Scenarios and Offer Events
Documentation on Product Flow with SDK integration examples:
- Android — Product scenarios and offer events;
- iOS — Product scenarios and offer events.
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 |
|
|
The user saw an offer or proposal |
|
|
The user started ordering a product |
|
|
An intermediate ordering step |
|
|
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
offerShowncan be omitted; - when
flowStartcan be omitted; - how to pass intermediate steps via
flowStep; - how to pass the final result via
flowResult; - how to use
payload,referrer, andstepOption; - 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)
Recommended statuses
|
Status |
Meaning |
|
|
Successful completion |
|
|
Declined by a third party |
|
|
Awaiting decision |
|
|
Cancelled by the user |
|
|
Offer or session expired |
|
|
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.