Many clients come to us with a problem: the product funnel looks like a black box. We see that after the add_to_cart step, 40% of users are lost, but the reason is unknown. Money goes into advertising instead of improvements. We solve this by implementing end-to-end tracking from scratch. Over 5 years, we've implemented tracking for 50+ mobile projects — from MVPs to enterprise apps with millions of users.
A conversion funnel without proper tracking is a diagram drawn from memory by a product manager. With tracking, it's numbers showing exactly where and for which user segment money slips through your fingers. A common mistake: track only the start and end of the funnel (checkout_started → payment_succeeded) and then wonder why 60% drop-off is unclear. The right approach is to track each step with enough context to explain the drop-off reason.
Why Standard Funnels Fall Short
Often, funnels are built from overly generic events: screen_viewed instead of checkout_payment_opened. Without granularity, it's impossible to know which screen or action causes churn. For example, if 30% of users drop off at payment_opened, it could be due to slow loading, missing Apple Pay, or a 3DS error. In our practice, after adding 3ds_required to the event properties, the drop-off on payment_initiated halved — simply because we saw that 80% of drop-offs went through 3DS and optimized that process.
How We Solve It: Type-Safe Wrapper Implementation
We design a type-safe wrapper for funnel events, ensuring each step has mandatory context. For example, on a fintech project, we noticed that the checkout step had a 50% drop-off. By adding properties like payment_methods_available and 3ds_required, we identified that users with only one payment method were 3x more likely to abandon. The fix was adding a fallback option, reducing drop-off by 25%.
Designing the Funnel
Before implementation, we define:
- Funnel steps — atomic events that unambiguously correspond to user progress
- Properties for each step — what needs to be known about the user and context at that step
- Drop-off points — what happens when a user doesn't proceed to the next step
Example e-commerce funnel:
| Step | Event | Key Properties |
|---|---|---|
| 1 | product_viewed |
product_id, category, source |
| 2 | add_to_cart |
product_id, quantity, cart_size |
| 3 | checkout_started |
cart_total, item_count, has_promo |
| 4 | checkout_address_completed |
address_type (new/saved) |
| 5 | checkout_payment_opened |
payment_methods_available |
| 6 | payment_method_selected |
method (card/paypal/apple_pay) |
| 7 | payment_initiated |
method, 3ds_required |
| 8 | payment_succeeded |
order_id, total, method |
Step 7 → 8 with 3ds_required = true gives a separate branch in the funnel. Without this property, it's unclear why there is more drop-off at this step.
Implementation Details
// Android — type-safe wrapper for funnel object CheckoutFunnel { fun trackStepCompleted(step: CheckoutStep, props: Map<String, Any> = emptyMap()) { val eventName = when (step) { CheckoutStep.ADDRESS -> "checkout_address_completed" CheckoutStep.PAYMENT_OPENED -> "checkout_payment_opened" CheckoutStep.PAYMENT_METHOD_SELECTED -> "checkout_payment_method_selected" CheckoutStep.PAYMENT_INITIATED -> "payment_initiated" CheckoutStep.PAYMENT_SUCCEEDED -> "payment_succeeded" CheckoutStep.PAYMENT_FAILED -> "payment_failed" } val baseProps = mapOf( "session_id" to sessionManager.currentSessionId, "cart_id" to cartManager.currentCartId, "user_id" to authManager.currentUserId, "timestamp" to System.currentTimeMillis() ) analyticsClient.track(eventName, baseProps + props) } } // Usage in ViewModel checkoutViewModel.onAddressConfirmed.observe(this) { address -> CheckoutFunnel.trackStepCompleted( CheckoutStep.ADDRESS, mapOf( "address_type" to if (address.isNew) "new" else "saved", "country" to address.country ) ) } // iOS — similar approach enum CheckoutStep { case addressCompleted(isNew: Bool, country: String) case paymentOpened(availableMethods: [String]) case paymentMethodSelected(method: String, requires3DS: Bool) case paymentSucceeded(orderId: String, total: Double, method: String) case paymentFailed(errorCode: String, method: String) } extension AnalyticsService { func track(checkoutStep: CheckoutStep) { let (eventName, props) = checkoutStep.analyticsPayload amplitude.track(eventType: eventName, eventProperties: props) } } extension CheckoutStep { var analyticsPayload: (String, [String: Any]) { switch self { case .paymentMethodSelected(let method, let requires3DS): return ("checkout_payment_method_selected", [ "payment_method": method, "requires_3ds": requires3DS, "cart_id": CartManager.shared.currentCartId ]) // ... } } } Building Funnels in Amplitude
In Amplitude Funnel Analysis:
// Amplitude Chart — configuration via UI or API { "chart_type": "FUNNEL", "steps": [ { "event_type": "product_viewed" }, { "event_type": "add_to_cart" }, { "event_type": "checkout_started" }, { "event_type": "payment_succeeded" } ], "funnel_type": "ordered", // strict order "conversion_window": 7, // 7 days to complete funnel "conversion_window_unit": "days", "segment_definitions": [ { "name": "iOS users", "filters": [{"subprop_key": "platform", "subprop_value": ["iOS"]}] }, { "name": "Android users", "filters": [{"subprop_key": "platform", "subprop_value": ["Android"]}] } ] } conversion_window is a critical parameter. For a product purchase, 7 days is reasonable. For a SaaS subscription, it might be 30 days. Too short a window lowers conversion.
Firebase Analytics Funnels
// Firebase funnel via Google Analytics // Configured in GA4 → Explore → Funnel Exploration // Via API: const { BetaAnalyticsDataClient } = require('@google-analytics/data'); const client = new BetaAnalyticsDataClient(); const [response] = await client.runFunnelReport({ property: 'properties/YOUR_PROPERTY_ID', funnelSteps: [ { name: 'Product Viewed', filterExpression: { filter: { fieldName: 'eventName', stringFilter: { value: 'product_viewed' } } } }, { name: 'Add to Cart', filterExpression: { filter: { fieldName: 'eventName', stringFilter: { value: 'add_to_cart' } } } }, { name: 'Purchase', filterExpression: { filter: { fieldName: 'eventName', stringFilter: { value: 'purchase' } } } } ], dateRanges: [{ startDate: '30daysAgo', endDate: 'today' }] }); Comparison of Tracking Tools
| Tool | Free Limit | Segment Flexibility | Session Replays |
|---|---|---|---|
| Amplitude | 10M events/month | High: cohorts, behavioral segments | No |
| Firebase Analytics | Unlimited (GA4) | Medium: only user segments | No |
| Mixpanel | 20M events/month | High: custom reports, projectors | No |
Amplitude enables faster funnel building than Firebase, especially with complex segmentation. Firebase wins on price — it is free for virtually any data volume.
Analyzing Drop-Off Causes
Raw funnel data says "we lose 40% here." It doesn't say why. To understand the causes:
- Session replays on the drop-off step (UXCam/Smartlook): what users did before leaving
- User properties in analytics: who leaves — new or returning, iOS or Android, which plan
- A/B test on the high-drop-off step: test a hypothesis for improvement
// Tracking abandonment reason class PaymentViewModel : ViewModel() { override fun onCleared() { super.onCleared() if (!paymentCompleted) { CheckoutFunnel.trackStepCompleted( CheckoutStep.ABANDONED, mapOf( "last_step" to currentStep.name, "time_on_step_seconds" to stepTimer.elapsed(), "error_shown" to lastErrorShown ) ) } } } How to interpret funnel results
After setting up tracking, it's important not only to look at percentages but also to compare segments. For example, conversion on iOS might be 15% while on Android it's 10%. That's a reason to check for differences in UI/UX across platforms. Also look at the time between steps: a sharp increase in time on a step may indicate user frustration.
Funnel description approach is based on Amplitude's Funnel Analysis documentation: event attributes and conversion window determine analysis accuracy.
What's Included in Our Service
- Audit of current events and taxonomy.
- Design of the full funnel map: atomic events with context.
- Implementation of type-safe wrappers in Kotlin/Swift.
- Integration with Amplitude/Firebase, setting up Funnel Report with segmentation.
- Connecting Session Replay on critical steps.
- Taxonomy documentation and team training in analytics.
- Ongoing support — adjusting events when logic changes.
Timeline and Pricing
Taxonomy design and implementation: 2–3 days. Dashboards and primary analysis: another 1–2 days. Pricing is determined individually after an audit. We'll assess your project within 1 day — contact us.
Why Choose Us
We have 5 years of experience in mobile analytics — we've implemented tracking in fintech, e-commerce, and SaaS. We guarantee that every funnel step will come with clear context, not just an event name. After implementation, you get not only numbers but a tool for daily optimization. Order an audit of your current funnel — we'll dissect your taxonomy and propose an action plan.
Get in touch with us for a consultation — we'll analyze your funnel and propose an action plan.







