If You’re Seeing Attribution Shifts on PWAMP, Try This

Default AMP Analytics beacons could be sending data that makes conversion attribution really messy. Thankfully we have a fix.

We have launched many Progressive Web App with AMP (PWAMP) projects for clients, giving their mobile sites fast page loads and the capability to run as a standalone app.

Usually after launch, we’ll report on performance, highlighting the engagement improvements that come with the improved speed and functionality. One thing we had noticed for PWAMP projects is that paid conversion performance tends to lag behind other traffic sources. Given the multiple input variables for paid ads, it was difficult to determine exactly what was going on.

Recently, however, we launched PWAMP for an ecommerce site, and we saw not only a dip in paid ad conversion performance, we saw a massive spike in organic and paid sessions. For example, here is a comparison of organic mobile clicks in Search Console with organic mobile sessions & users from Google Analytics:

Once the PWAMP launched, organic sessions nearly doubled.

Visitors to the site often browse a lot before making a purchase, and their main paid ad landing pages were category pages. So a visitor interaction could be:

  1. Land on category page
  2. Look at product A
  3. Go back to category page
  4. Look at product B
  5. Add to cart
  6. Start checkout process

In actuality, the visitors are looking at 10-20 products, but this saves space and gets the point across. When we looked closer at the analytics collection beacons, the problem was clear: the referrer that tracks the domain where a user came from to reach our site – set in variable dr for Google analytics – never changes on page navigation. The above session looks like this:

Page dr: value Attribution
Landing Page www.google.com Paid Ads
Product A Page www.google.com Organic Traffic
Back to Landing Page www.google.com Paid Ads
Product B Page www.google.com Organic Traffic

For a progressive web app site, that means the first click will have the referral data, but so will every other click. But those other clicks won’t have the relevant Google Ads data, particularly the gclid tag, so Analytics will see it and think they’re referral traffic from Google.

Each switch from Paid Ads to Organic Traffic is treated as a new session, so now this one session with a transaction is being seen as four sessions (two Ads sessions, two organic sessions,) with one of the organic sessions earning the transaction attribution.

How did we fix it?

PWA’s behave like a single-page web application; the referrer saved in the document state never changes. Further, document.referrer is a read-only variable; it cannot be set with JavaScript. Since we could not hope to spoof the referrer at the document level, we looked for a way to modify the collection beacon requests before they were sent. Fortunately, the AMP team made this possible with the platform and amp-analytics variable externalReferrer. It’s worth noting that the seemingly more appropriately named variable documentReferrer cannot be modified in the same way.

The externalReferrer variable can be set as a vars parameter in amp-analytics JSON. For example:

<amp-analytics type="googleanalytics">     <script type="application/json">     {       "vars": {         "account": "UA-000000-1",         "externalReferrer": ""       },       ...     }     </script> </amp-analytics>

We take advantage of this feature by modifying the amp-analytics JSON before an AMP page is injected into the shadowRoot. This can most easily be accomplished by fetching and parsing AMP HTML into a Document object and then searching for and modifying each relevant amp-analytics script. Here’s a snippet for Google analytics that sets the referrer to an empty string and preserves the rest of the amp-analytics JSON:


// Blank the referrer in Google analytics after first navigation if (!isFirstNavigation) {     const googleanalytics = ampbody.querySelectorAll('amp-analytics[type="googleanalytics"] script');     Array.from(googleanalytics).forEach(analytics => {         try {             const analyticsJson = analytics.textContent;             if (analyticsJson) {                 const analyticsObj = JSON.parse(analyticsJson);                 if (!analyticsObj.vars)                     analyticsObj.vars = {};                 analyticsObj.vars.externalReferrer = '';                 analytics.textContent = JSON.stringify(analyticsObj);             }         } catch (ex) {             console.error('Unable to update amp-analytics\n', ex);         }     }); }

After this modification, we inspect the https://www.google-analytics.com/r/collect beacons sent on page navigation and are happy to find dr is indeed empty (beyond the landing page).

What does this mean going forward?

As AMP and PWA continue to mature, we’ll be working to ensure clients get the most from these platforms while maintaining the reporting and data accuracy that is needed when operating their business.