Morgan Avenia Taking work

Work → Cannabis retail · Minnesota

The conversion count that meant nothing

Three hundred and seven conversions. Two orders. The account had been optimizing toward an age gate for a full quarter.

One quarter of paid search

Google Ads reported 307conversions
Orders in the POS 2orders

Reported ROAS was healthy. Actual ROAS on the spend was 0.33.

The setup

A dispensary menu you can't put a tag inside.

Retail cannabis in most states runs its menu and checkout inside a third-party iframe. The site is yours. The transaction isn't. Nothing that happens after the click into that menu is visible to your container, and the platform will happily report on whatever proxy event you gave it instead.

Here the proxy was an age gate pass. Every visitor who confirmed they were 21 counted as a conversion, which is why the number was enormous and useless. Bid strategy had been learning on it for months.

What was broken

Five failures, stacked.

  • Conversion actionGoogle Ads was counting age gate passes, not purchase intent.
  • Menu click trackingThe function that flagged a click into the menu was defined but never fired in the real user flow.
  • Commerce eventsThe menu's own add-to-cart and begin-checkout messages were reaching the page and going nowhere. No triggers were listening.
  • UTM persistenceAttribution parameters were held in local storage only, which drops for Safari ITP and private browsing.
  • Purchase signalNo confirmation event was ever captured from the iframe, so the funnel had no floor.

The rebuild

Give the platform something real to learn on.

Capture click IDs and campaign parameters on landing, persist them in a way that survives ITP, and stitch them onto the menu handoff so the session carries identity across the boundary. Then listen properly: bind the menu click to the elements users actually press, forward the iframe's commerce messages into the data layer, and stand up a real purchase event through server-side measurement instead of hoping the browser cooperates.

The conversion action was repointed at purchase intent, and the age gate went back to being an age gate.

Reconciliation dashboard

A React app that ingests exports from analytics, the ads platform, and the POS, then puts the three side by side. When they diverge you can see the day it started.

Compliance first

Everything shipped inside a state-specific content framework, so nothing in the tracking or the creative created a regulatory problem while solving a measurement one.

Result

A number the client can defend.

Paid search now reports orders that appear in the POS, and the gap between the two systems is small enough to explain. Campaign structure was rebuilt on top of that across brand, non-brand, competitor, and test, because segmentation only means something once the measurement underneath it is honest.