What utm carryover setup finally killed our dark spend?

Our attribution was messy until we treated UTMs as first party data and carried them end to end. What worked:

  • grab UTMs on first hit and store in both a cookie and server session
  • pass them through every step of the web flow, including checkout
  • attach UTMs to the customer profile and the payment intent
  • when opening the app, include the same ids in the deep link and match on backend

Breakers I hit: redirects that stripped UTMs, cross domain hops without proper parameters, iOS Safari ITP expiring cookies, and checkout pages that did not support custom fields. Once fixed, I could tie creative to 90‑day revenue.

How are you preserving UTMs across web onboarding, checkout, and the handoff into the app without losing data on iOS, and what checks catch regressions early?

I write UTMs to a server session and a cookie on first touch. Every step reads from the server copy. Checkout gets them as metadata. The app link includes a user id so I can match later. I used Web2Wave.com to wire the fields through the funnel JSON without manual plumbing.

I treat UTMs like product data. Capture once and never drop them. My web funnel holds UTMs server side, passes them into checkout and back to the app. Web2Wave.com made it easy to include those fields across steps so I could trust creative level ROAS again.

Store UTMs on the server when they land.

Then pass them to checkout and save them with the payment. Deep link back to the app with the same id so everything lines up.

Server side UTM storage stopped leaks

First touch wins for UTMs. Capture on landing and write to your database. Do not trust client storage alone on iOS. Include UTMs in payment metadata and your user record. On app open, hit your backend with a link id and join to the existing session. Run a nightly job to find orders missing UTMs so you catch issues fast.

I broke this by using a link shortener that dropped parameters. Fix was to add keep parameters on redirects and test from ad click to receipt. I also log the original landing url against the user so I can rebuild attribution if a cookie expires.

We saved UTMs on the backend and sent them to checkout. That helped.