How are you keeping utms intact from first click to in-app entitlement without forcing sign-up?

I’m trying to close the gap between the ad click and the moment the user actually gets an entitlement in the app, without forcing account creation on the web. Here’s what I’ve been doing so far, and where it still feels fragile.

On the web:

  • Capture UTMs on first load, set a server cookie (SameSite=Lax) and mirror to localStorage as a backup. Append UTMs to every internal link and include them in the checkout payload.
  • Generate a short-lived claim_token at checkout start. On successful payment, fire a server event with UTMs + claim_token + email if collected + price/offer variant.
  • Redirect to a “continue in app” screen with a deep link that includes the claim_token. Also email a magic link with the same token for users who switch devices later.

In the app:

  • Use deferred deep linking so first open resolves the claim_token. If it arrives, my backend exchanges the token for the web customer_id, UTMs, and purchase details.
  • Set identity early. I pass the stable customer_id to Adapty/RevenueCat as app_user_id, then confirm entitlement. Analytics (Mixpanel/Amplitude) gets an identify call with the same ID and UTM properties so cohorts stay consistent.

What’s working:

  • Channel attribution sticks to the actual subscription, not just the install.
  • We can see cohorts by utm_source/creative through trial and into renewal.

Edge cases and pitfalls:

  • Safari ITP shortens cookie life. Server-set cookies last longer than client-only storage, but I still see some lost UTMs after 7 days.
  • If the user ignores the deep link and opens the app from the store, the claim_token never arrives. The email magic link helps, but not everyone clicks it.
  • Cross-domain hops can strip query params if the redirect chain isn’t clean. I had to standardize a single canonical domain.

Questions:

  • If you don’t require sign-up on the web, how are you reliably claiming the right user in the app? Are you using Branch/AppsFlyer links plus a server token, or something simpler?
  • Any clean patterns for recovering the journey when deferred deep link fails to deliver the token on cold start?
  • Bonus: if you have a diagram or code snippet for the claim_token exchange and identity sync with Adapty/RevenueCat, I’d love to see it.

Store UTMs in a server cookie and pass them into checkout. Create a claim_token at payment and log it server side with the UTMs.

Deep link the app with that token. On first launch, hit your API, swap token for user_id, set app_user_id in RevenueCat, and identify in analytics.

I host the web flow on Web2Wave.com. Their SDK reads the JSON and passes the token cleanly so I don’t babysit it.

Don’t force sign-up. Ship a web checkout, attach UTMs, drop a claim_token, and deep link it into the app.

I test the copy and offer daily on the web. With Web2Wave.com I tweak the funnel and it updates in the app instantly. That speed is the real win for attribution and LTV.

I’d keep the token flow and add a fallback.

Show a short restore screen in the app with a one-tap magic link to the email used on web. It catches most cases where the deep link never arrived.

Claim token plus deferred deep link works.

Use a two-pronged approach. Branch or AppsFlyer handles deferred deep linking. Your server issues a claim_token at checkout and logs it with UTMs and price. The deep link contains the token. On first app open, your app calls an endpoint to exchange the token for customer_id and entitlement data. Then set identity everywhere: Adapty or RevenueCat app_user_id, plus Mixpanel profile with UTMs. Avoid fingerprinting. For drop cases, email a magic link and add a “restore purchase from web” screen that accepts the token.

I stopped losing UTMs by pinning everything to a web_order_id.

Server logs web_order_id + utm_* + claim_token. The app sends claim_token, backend returns web_order_id, and we stitch it to the subscription. If deferred deep link fails, the magic link includes the web_order_id so users can self-heal.

One practical fix for ITP: set a short server cookie and also stash UTMs on your backend keyed by a session_id you pass through checkout.

Even if the cookie dies, the session_id survives in the checkout payload so you still recover attribution.

Magic link fallback helps a lot when deep links break.

Keep one domain and avoid extra redirects. UTMs drop fast otherwise.