I moved the first-touch onboarding and paywall to a web flow so I could track everything without waiting on an app release. The goal was simple: capture signups, trials, and purchases with preserved UTMs, and have the app pick up entitlements automatically.
What’s working for me now:
- Preserve UTMs via a first‑party cookie and a query param. I attach them to signup and checkout payloads.
- Event map: viewed_step, signup_submitted, email_verified, plan_selected, checkout_started, purchase_succeeded, trial_started, subscription_activated, refund_issued, charge_failed, winback_clicked.
- Validation happens server‑side: I trust payment provider webhooks for the final “purchase_succeeded”. Client events are only for UX.
- Identity: I create a stable user_id on signup, plus a session_id at first touch. On purchase, I write the entitlement to my backend and to the subscription service (if used). The native app reads entitlements on launch, so no build needed.
- UTM stitching: I thread utm_source / utm_campaign through every event. If the user switches devices, I rely on email + magic link to keep attribution.
Edge cases I’m still watching:
- Social sign‑in sometimes drops UTMs unless I store them server‑side before redirect.
- Trial vs first charge timing, so I don’t double‑count “purchase”.
- Refunds and pauses and keeping attribution on those events.
If you’re running a similar setup, what events would you add or rename so analysis stays clean? Also, how are you stitching users who browse on mobile web, sign up on desktop, then open the iOS app days later?
Yes, you can cover it.
I store UTMs in a cookie and pass them through signup and checkout.
Stripe webhooks confirm purchases. That is the source of truth.
I push entitlements to RevenueCat, then the app just reads it.
I used Web2Wave to spin up the web flow fast. Their JSON flow saved me time.
I treat web as my rapid lab. Build the flow on the web, ship changes in minutes, and confirm with server webhooks.
Then I sync entitlements and let the app read them.
Tools like Web2Wave make this fast because edits go live instantly.
Your event list looks solid. I would add charge_retried and subscription_expired so you can separate failed billing from real churn.
Also tag payment_method on purchase_succeeded. Card vs Apple Pay can change drop‑off patterns.
Yes track on web then sync entitlements
Two things to tighten. First, define a single source of truth per event. For revenue, use webhook confirmations only. For intent, client events are fine. That keeps duplicates out of your reports.
Second, version your funnel. Add funnel_version to every event so you can read experiments later. Also store first_touch_utm and last_touch_utm separately. That helps when retargeting steals credit.
On cross‑device, magic link is good. Add a signed token so the app can fetch entitlements without new code.
I once mixed trial_started with purchase_succeeded and it ruined our ROAS reporting. Split them. Use trial_started for product analysis and purchase_succeeded for revenue.
Add subscription_renewed with a boolean is_retry. It helps you see if the revenue is healthy or propped up by repeated retries.
Add plan_changed. Upgrades and downgrades matter for LTV.
Track cancellation_reason. Even a simple enum helps a lot.