We moved signup and first purchase to a web flow. The goal: tie first-touch UTMs and specific onboarding steps to who actually pays and how much. Here’s the setup that finally gave me user-level revenue without guesswork:
- On first landing, grab utm_*, click ids (gclid/fbclid/msclkid), referrer, and timestamp. Store in a first-party cookie and localStorage. Generate a prospect_id (UUID).
- Carry prospect_id through the funnel. Add it to URLs (or a server session) so router changes don’t drop params. SameSite=Lax on cookies.
- Log every onboarding event with prospect_id + utm_* to analytics and warehouse.
- On account creation, stitch prospect_id to user_id (email or internal id). Dedupe if they restart onboarding.
- Checkout: pass prospect_id and utm_* in checkout session metadata. On payment webhooks, write revenue_event with user_id, prospect_id, plan_id, currency, MRR (or first charge value), and trial info.
- Renewals and cancellations: listen to subscription.updated; decide your rule set. I attribute first payment to acquisition UTMs, then track expansions/renewals to lifecycle touches separately.
- App handoff: deep link from the last web step into the app with prospect_id. On first app open, send an identify call to stitch device_id to user_id.
- Privacy blockers: Safari ITP cuts cookie life. I backfill with link decoration + server-side session + email hash after signup.
- QA: reconcile daily revenue from the gateway vs warehouse sums. Spot-check five users/week from ad click to payment event. Fix gaps fast.
- Gotchas I hit: canonical redirects stripping UTMs, third-party checkout domains losing cookies, Apple Pay flows not returning query params, support refunds not mirrored in analytics, and trials causing late revenue that some teams mis-attribute to later touches.
This got us 88–93% of new subs mapped to a first-touch UTM with actual paid amounts. Channel LTV stopped being guessy.
Question: how are you handling multi-touch and cross-device? Do you keep acquisition credit on first click after a trial, or switch to last paid click? Any tips to avoid UTM loss with Apple Pay/Google Pay flows?