Categories
Attribution Digital Marketing Google Ads

YouTube Video Campaigns are Over-Reporting Conversions

YouTube Video Campaigns count Conversions very differently from other Campaign types

YouTube Video Campaigns count view-conversions as click-conversions, and include them in the “Conversions” columns of Google Ads.

If you are running a ROI focused YouTube Campaign that is targeting a lower funnel Remarketing Audience, the revenue and conversions reported by Google Ads can be over-inflated by 1000% or more vs what Google Analytics will report.

When running a YouTube Campaigns, always:

  1. Be suspicious and skeptical of the conversion data
  2. Adjust your performance targets. Conversions will be overinflated by a factor of up to 1000%, so adjust your performance accordingly.
  3. Use Google Analytics as your measure of YouTube Campaign performance
  4. Avoid Targeting lower funnel Remarketing Lists with YouTube. Save these lists for campaigns that offer true click-based conversion tracking.
  5. Explicitly exclude low funnel Remarketing Lists from your targeting. This will ensure the attribution poaching is limited.

YouTube Conversion Tracking Explained

Standard Campaign Behaviour

For most campaign types, a conversion is only recorded in the “Conversions” column when someone clicks on your ad and then proceeds to make a purchase. This is called a Click-Through Conversion. If there are no clicks on your ad, no conversion is recorded in the Conversions column.

This is the normal expected behaviour.

YouTube Campaign Behaviour

For YouTube campaigns, clicks behave normally: If someone clicks on your ad and then makes a purchase, that purchase is recorded as a conversion as expected. So far so good.

However, if someone DOES NOT CLICK on your ad, but watches your entire ad, and then later purchases, that purchase will be recorded as a conversion and attributed to YouTube.

As per Google:

A ‘view’ is counted when someone watches 30 seconds (or the whole ad if it’s shorter than 30 seconds) or clicks on a part of the ad. A ‘view’ that leads to a conversion is counted in the ‘Conversions’ column. 

Google Ads Help: Understand your conversion tracking data
https://support.google.com/google-ads/answer/6270625

Essentially, these are View-Through Conversions masquerading as Click-Through Conversions. The ad was never clicked, yet a conversion was recorded anyway. Considering that many ads are only 5 seconds long, most ad views are likely being treated as clicks.

This is NOT EXPECTED behaviour!

Attribution Poaching

Attribution Poaching is when one Channel tries to take credit for a sale that was either going to happen anyway, or for a sale that was actually caused by another Channel.

Consider this scenario

You create a YouTube campaign targeting your Shopping Cart Abandoners with a 5 second video ad.

One of your potential customers visits your site, and ads a product to their Shopping Cart. By adding to the Shopping Cart, they are now in your Shopping Cart Abandoners audience, and will be actively targeted by your YouTube Campaign.

They continue to browse your site, but they are interrupted by an e-mail or text from a friend with a link to a funny YouTube video. They click on the link and watch the video. While watching the video, they are forced to watch your 5s video ad (which they ignore). Since they watched y0ur entire ad, Google considers that a “click”.

They then eventually return to your site, to complete the purchase. Or maybe they complete the purchase 3 days later after receiving your Cart Abandonment e-mail.

In either case, your YouTube Campaign will claim credit for the Purchase, and will count it as a Conversion even thought that customer never clicked on your ad.

Why is Google Doing This?

A video view is much more like an impression than like a click, so why are these conversions being lumped in with click-through conversions?

The simple cynical answer is that Goolge makes more more money this way. By counting view-through conversions, the YouTube campaigns will appear to perform much better than they actually do with just click-through conversions (as much as 1000% better). If advertisers think that YouTube is performing 10X better, then they will allocated 10X more budget. The end result is Google makes 10X more money from YouTube.

A “Video View” is more like an Impressions than a Click

Video-View-Conversions should clearly be lumped into the View-Through Conversion column. If Google wants to explicitly report on Video-View-Conversions separately, then they should create another column type. Don’t lump them in with click-through conversions.

Unexpected Behaviour

The problem with all this is that suddenly the “Conversions” column behaves differently in one campaign type vs another. Suddenly the clean click-through conversion data is being polluted with View-Through conversion data. This makes YouTube campaigns appear to perform much better than they should. Which will lead you to incorrectly increase spend.

To make the matter worse…

  1. Many people have long click-through conversion windows. Often 30 days or even longer. This essentially allows a YouTube to claim credit for a conversion that happens 30 days after a video view.
  2. Many video ads are short – only 5s long. Most of these short ads are probably viewed in their entirety, meaning that they are all being counted as clicks. Often the user is forced to watch the entire 5s ad – again a click. This leads to more incorrectly attributed click-conversions.
  3. Often people target video ads using remarketing lists. Often the lowest hanging fruits for video campaigns are Remarketing Audiences. This shows ads to people who know your brand, who have recently visited your site, who may be subscribed to your newsletter, and who may in fact be currently actively shopping on your site. They are all likely to buy from you regardless of the the video ad, but the video ad will take credit for all their purchases.

This could also affect your Display and Discovery Campaigns

This issue could also affect your regular display campaigns and possibly your Discovery campaigns if they are serving ads on the YouTube network.

My recommendation for Display Campaigns is to try and exclude all YouTube placements: Go to: Campaign Settings > Additional Settings > Content Exclusions and select all of the following for exclusion:

  • Live streaming YouTube video
  • Embedded video
  • In-video

Notes:

  • This should only be an issue if you use Google Ads Native Conversion tracking. If you import your conversions directly from Google Analytics, then this should not be an issue (as Analytics only attributes conversions to the last click)
  • If you are running  a pure brand awareness campaign, this is probably less of a concern for you.
  • If you are an Ad Agency getting paid as a % of ad spend, then YouTube campaigns can make you a lot of money.

More Reading

Categories
Digital Marketing Google Ads Shopify

Google Ads Dynamic Remarketing for Shopify

Last Updated: September 25, 2020

STEP 1
Activate Dynamic Remarketing in Google

  1. Go to Google Ads > Tools & Settings > Shared Library > Audience Manager > Audience Sources
  2. Click Set up tag in the “Google Ads tag” card
  3. Remarketing: Choose “Collect data on specific actions…
  4. Business Type: Choose “Retail
  5. Retail Parameters: Select All Parameters
  6. Click Save and continue.
  7. Click on Install Tag Yourself
  8. In the first code box, look for the number at the end of the first line of code and write it down or copy it. This is your Google Conversion Id — you will need it in the next step.
<!-- Global site tag (gtag.js) - Google Ads: 123456789 -->
  1. Click Continue and then Done

STEP 2
Install the Remarketing Code in Theme.liquid

In your store’s admin section go to:

  • Online Store > Themes > Actions > Edit Code
  • Expand the Snippets section and choose Add new snippet
  • Call the snippet “adwords-remarketing
  • Paste the code below into the snippet
  • Enter your google_conversion_id that you obtained in Step 1.8 above.
{% comment %}
Google Ads Dynamic Remarketing Script by Alex Czartoryski
https://business.czarto.com/2017/02/07/shopify-dynamic-remarketing-setup/

This version: Sept 30, 2020
The latest version of this script available here:
https://github.com/Czarto/ShopifyScripts/blob/master/snippets/adwords-remarketing.liquid
{% endcomment %}

{% comment %}Set to false if GTAG is already loaded on the page. Leave to true if unsure.{%endcomment%}
{% assign load_gtag = true %}

{% comment %} Enter your google conversion id below {% endcomment %}
{% assign google_conversion_id = 123456789 %}

{% assign shopify_store_country  = 'US' %}
{% if shop.currency == 'CAD' %}
{% assign shopify_store_country  = 'CA' %}
{% elsif shop.currency == 'AUD' %}
{% assign shopify_store_country  = 'AU' %}
{% endif %}

{%if load_gtag %}
<!-- Global site tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-{{ google_conversion_id }}"></script>
{% endif %}
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'AW-{{ google_conversion_id }}');
</script>

{% assign google_event = false %}
{% assign google_items = false %}
{% assign google_value = false %}
{% if template contains 'cart' %}
	{% assign google_event = 'add_to_cart' %}
	{% capture google_items %}{% for item in cart.items %}{'id':'shopify_{{ shopify_store_country  }}_{{ item.product.id }}_{{ item.variant.id }}','google_business_vertical': 'retail'}{% unless forloop.last %}, {% endunless %}{% endfor %}{% endcapture %}
	{% assign google_value = cart.total_price %}
{% elsif template contains 'collection' %}
	{% assign google_event = 'view_item_list' %}
	{% capture google_items %}{% for item in collection.products limit:5 %}{'id':'shopify_{{ shopify_store_country  }}_{{ item.id }}_{{ item.variants.first.id }}','google_business_vertical': 'retail'}{% unless forloop.last %}, {% endunless %}{% endfor %}{% endcapture %}
{% elsif template contains 'product' %}
	{% assign google_event = 'view_item' %}
	{% capture google_items %}{'id':'shopify_{{ shopify_store_country  }}_{{ product.id }}_{{ product.selected_or_first_available_variant.id }}','google_business_vertical': 'retail'}{% endcapture %}
	{% assign google_value = product.selected_or_first_available_variant.price %}
{% elsif template contains 'search' %}
	{% assign google_event = 'view_search_results' %}
	{% capture google_items %}{% for item in search.results limit:5 %}{'id':'shopify_{{ shopify_store_country  }}_{{ item.id }}_{{ item.variants.first.id }}','google_business_vertical': 'retail'}{% unless forloop.last %}, {% endunless %}{% endfor %}{% endcapture %}
{% endif %}

{% if google_event %}
<script>
	gtag('event', '{{ google_event }}', {
	  'send_to': 'AW-{{ google_conversion_id }}',
	  {% if google_value %}'value': '{{ google_value | divided_by: 100.0 }}',{% endif %}
	  'items': [{{ google_items }}]
	});
</script>
{% endif %}

The latest version of this code is available on Github

Add snippet to your Theme file

Open up Layout > theme.liquid and add the following line of code before the closing </head> tag:

{% include 'adwords-remarketing' %}

STEP 3
Install Remarketing in the Checkout Scripts

  • In the very bottom left hand corner the Shopify Admin choose Settings and then Checkout
  • Scroll down to the Additional Scripts section.
  • Copy and paste the code below into the “Additional Scripts” field and update google_conversion_id with your value from step 1.8 as before.
{% comment %}
Google Ads Dynamic Remarkting Script by Alex Czartoryski https://business.czarto.com/

This version: Sep 30, 2020
The latest version of this script available here:
https://github.com/Czarto/ShopifyScripts/blob/master/settings/checkout/adwords-remarketing.liquid
{% endcomment %}

{% comment %}Set to false if GTAG is already loaded on the page. Leave to true if unsure.{%endcomment%}
{% assign load_gtag = true %}

{% if first_time_accessed %}
{% comment %} Enter your account specific values below {% endcomment %}
{% assign google_conversion_id = "123456789" %}

{% assign shopify_store_country  = 'US' %}
{% if shop.currency == 'CAD' %}
{% assign shopify_store_country  = 'CA' %}
{% elsif shop.currency == 'AUD' %}
{% assign shopify_store_country  = 'AU' %}
{% endif %}


{%if load_gtag %}
<!-- Global site tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-{{ google_conversion_id }}"></script>
{% endif %}
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'AW-{{ google_conversion_id }}');
</script>

<!-- Event snippet for Web Order conversion page -->
<script>
    // Google Ads Remarketing
    gtag('event', 'purchase', {
	  'send_to': 'AW-{{ google_conversion_id }}',
	  'value': '{{ total_price | divided_by: 100.0 }}',
	  'items': [{% for item in order.line_items %}{'id':'shopify_{{ shopify_store_country }}_{{ item.product.id }}_{{ item.variant.id }}','google_business_vertical': 'retail'}{% unless forloop.last %}, {% endunless %}{% endfor %}]
	});
</script>

{% endif %}

Download the lastest version from Github

STEP 4
Verification

Once you’ve installed all your code, it’s time to run through your main pages (collection, product, cart, and purchase pages) with Google Tag Assistant installed to make sure there are no errors.

Next Steps
Configure your Remarketing Audiences

Now that your store is collecting dynamic remarketing data, the next step is to properly organize and segment your visitors into Purchasers, Cart Abandoners and Product Viewers. This is covered in the next post about Google Ads Remarketing Audiences.

Additional Reading…

Categories
Digital Marketing Google Ads

Google Ads Remarketing Campaign Structure

Remarketing Theory

  1. The deeper a user is in your sales funnel, the more likely he is to buy. A shopping cart abandoner is more likely to buy than a product browser who is more likely to buy than someone who briefly visited your homepage.
  2. The more recent the user’s visit, the more likely he is to buy. A user who visited your site yesterday is more likely to buy than a user who visited your site 10 days ago.
  3. The more likely a user is to buy, the more you want to bid on that user.

Audiences

Please read the post Essential Google Ads Remarketing Audiences and follow the instructions to create your remarketing audiences.

Campaign Setup

Create one campaign per major market you are targeting, and give them a descriptive name:

  • USA: Display Remarketing
  • Canada: Display Remarketing

Generally, but not always, you will want a separate campaign for every unique currency and language you are targeting.

Core Ad Groups

1. Cart Abandoners

This ad group will target cart abandoners: Visitors who added a product to their cart but never purchased. Dynamic Product ads perform particularly well with cart abandonment, as your visitors are shown the exact products that they added to their cart.

Audiences:

  • Cart Abandoners – 7 day
  • Cart Abandoners – 14 day
  • Cart Abandoners – 30 day
  • Cart Abandoners – 90 day
  • Cart Abandoners – 180 day

2. Product Viewers

This ad group will target users who visited a product details page but who never added a product to their cart.

Audiences:

  • Product Viewers – 14 day
  • Product Viewers – 30 day
  • Product Viewers – 90 day
  • Product Viewers – 180 day
  • Product Viewers – 365 day
  • Product Viewers – 520 day

3. Past Buyers

This ad group will show ads to people who have previously purchased. This is a good place to push micro conversions such as joining a loyalty program, joining a community, new product launches or related product up-selling.

Audiences

  • Past Buyers 14 days
  • Past Buyers 30 days
  • Past Buyers 90 days
  • Past Buyers 365 days
  • Past Buyers 520 days

Other Tips

Exclude Mobile App Placements

Exclude placements where users are unlikely to interact with your ad, or where they may accidentally click your ad, such as in mobile apps and games.

To exclude mobile apps, go to your ad group and then select:

  • Placements > Exclusions tab > Exclude placements
  • App Categories > Expand All App Categories, and exclude all app categories individually
  • Repeat for all your display ad groups

Exclude YouTube Placements

YouTube tracks View Through Conversions as if they were Click Through Conversions. This leads to attribution poaching, and makes your display campaigns appear to perform much better than they actually do. This ultimately causes you to increase bids and budgets, and overspend.

To exclude YouTube placements, go to: Campaign Settings > Additional Settings > Content Exclusions and select all of the following for exclusion:

  • Live streaming YouTube video
  • Embedded video
  • In-video

Exclude GMail Placements

Gmail “clicks” don’t necessarily result in a visit to your site, and usually only represent the expansion of your ad. This can lead to attribution poaching, in particular if you have a newsletter that you send our regularly.

To exclude gmail placements, go to Placements > Exclusions and exclude mail.google.com

A word about View Through Conversions

View Through Conversions are conversions where a display ad appeared on the screen, was NOT clicked, but the user ended up purchasing on your site sometime later. In general I recommend that everyone IGNORE View Through Conversions, in particular in remarketing campaigns.

What usually happens, is that an ad is displayed on screen, the visitor may not even see it, but clicks instead on a cart-abandonment e-mail and makes the purchase. AdWords will credit that conversion to the view through.

The one exception is for “brand unaware” customers. These are customers that have never visited your website before. If such a customer sees you ad, and purchases, then the odds are better that it was a result of your ad.

In an ideal world, there would be a simple way to test the value of your view-through-conversions, as they are different for every segment, and every business.

Other Resources

Categories
Analytics Digital Marketing Google Ads

Why doesn’t my Ad Spend Scale?

Consider this Scenario:

You spend $1,000 on a new Ad Campaign that generates $10,000 in revenue. “That’s a great ROI” you tell yourself, “Let’s double the spend!“.

When you double the budget to $2,000, your Campaign only generates $12,500 in total sales and not the $20,000 you were expecting. Why?

SpendSalesCost of SalesROAS
Ad Campaign #1$1,000$10,00010%1000%
Ad Campaign #2$2,000$12,50016%625%
Increasing spend by $1,000 only resulted in $2,500 in additional revenue: An incremental cost of sales of 40% and an incremental ROAS of 250%

Why doesn’t it scale?

By scale I mean that your ROI should be linear: If the first $1,000 generates $10,000 in sales, then the next $1,000 should also generate $10,000 in sales.

The issue is that rarely is your campaign performance evenly distributed. If your drill down deeper into your initial $1000 Campaign, you might see the spend broken down into something like this:

SpendSales%COSROAS
Branded$200$8,0002.5%4000%
Unbranded$800$2,00040%250%
$1000 Campaign$1,000$10,00010%1000%
Majority of the sales are being generated by a small subset of the overall campaign. A classic 80/20 scenario. The performance of the Branded ad set is subsidizing the cost of the unbranded ad set.

The performance of the Branded subset is subsidizing the cost of the Unbranded subset. 80% of your sales are coming from only 20% of the spend, while 80% of your spend is going towards an underperforming segment.

When we try to double the budget to $2,000, here is how the budget gets allocated:

SpendSalesCOSROAS
Branded$200$8,0002.5%4000%
Unbranded$1,800$4,50040%250%
$2,000 Campaign$2,000$12,50016%625%
When budget is doubled, most of the spend goes towards the underperforming segment, resulting in disappointing incremental sales.

With this new data in mind, we should probably:

  1. Decrease budget on the Unbranded segment
  2. Increase budget on the Branded segment

However, it is probably the case that your performing segment is already receiving 100% reach/impressions. So spending more is usually not possible. (In particular for Google Search Ads targeting your branded term: How much you can spend is a function of how many people are searching for your brand. Once you reach everyone, spending more can’t get you more people).

If you want to increase your spend, the only place to do so is in the underperforming unbranded campaign. But at least you’ll have a better expectation of the results.

Summary

  • Avoid making budget decisions on aggregate data. Always try to segment and dig a little deeper.
  • Don’t let underperforming segments ride the coat tails of your top performers. Look for 80/20 campaign and ad group performance and analyze those individually.
  • For Google Search Ads, always separate your Branded search terms and Unbranded search terms into separate campaigns.
Categories
Digital Marketing Google Ads Shopify Shopping

Add Microdata to your Shopify Product Pages (with variant support)

If you are using Google Merchant Center to with Shopify then you have likely ran into an issue where Merchant Center will give you a warning that there is insufficient match of micro-data information and that automatic item updates are no longer being performed.

merchant-center-warning
Insufficient micro-data warnings in Google Merchant Center

This is generally due to incorrect or incomplete micro-data on your product page.

Product.liquid

All the required edits should be limited to your Product.liquid file. You need to define a Product itemscope which will have properties such as Product Url, Product Image, Product Title, and Product Description.

Nested within the Product will be an Offer itemscope that will contain the product variant’s price, currency, condition, and availability.

What complicates things is that most Shopify Themes will have at least a partial implementation of micro-data, and are perhaps only missing a few items, or perhaps don’t fully support variants.

A good idea would be to first run your product page through Google’s Structured Data Testing Tool to see which tags already exist.

Product ItemScope

1. Open your product.liquid file and add the Product itemscope property to the outer most div. The first line of your product.liquid should look something like this: 

<div itemscope itemtype="http://schema.org/Product">

2. Directly below this line, you should add the product variant’s url and image markup as so:

<meta itemprop="url" content="{{ shop.url }}{{ product.selected_or_first_available_variant.url }}" />
<meta itemprop="image" content="https:{{ product.selected_or_first_available_variant.image.src | product_img_url: 'grande' }}" />

3. Find where your product’s title and description are displayed, and add itemprop=name and itemprop=description attributes as shown below:

<!-- Product Title -->
<h1 itemprop="name">{{ product.title }}</h1>
<!-- Product description -->
<div class="product-description" itemprop="description">
{{ product.description }}
</div>

Offer ItemScope

4. Now you need to find the place in your product.liquid file where you display your price. Find a wrapping div tag and add the Offers itemscope attributes to the tag. The Offers itemscope MUST nested within the Product itemscope div tag.

<div itemprop="offers" itemscope itemtype="http://schema.org/Offer">

5. Immediately after this line you can add the product variant’s price, currency, condition, and availability microdata as so:

<meta itemprop="priceCurrency" content="{{ shop.currency }}" />
<meta itemprop="price" content="{{ product.selected_or_first_available_variant.price | money_without_currency | remove: ',' }}" />
<meta itemprop="itemCondition" itemtype="http://schema.org/OfferItemCondition" content="http://schema.org/NewCondition"/>
{% if product.selected_or_first_available_variant.available %}
  <link itemprop="availability" href="http://schema.org/InStock" />
{% else %}
  <link itemprop="availability" href="http://schema.org/OutOfStock" />
{% endif %}

Putting it all together

Once done, your product.liquid should have roughly the following structure.

<!-- BEGIN Product itemscope -->
<div itemscope itemtype="http://schema.org/Product">
  <meta itemprop="url" content="{{ shop.url }}{{ product.selected_or_first_available_variant.url }}" />
  <meta itemprop="image" content="https:{{ product.selected_or_first_available_variant.image.src | product_img_url: 'grande' }}" />
 
  <!-- Product Title & Description -->
  <h1 itemprop="name">{{ product.title }}</h1>
  <div class="product-description" itemprop="description">
  {{ product.description }}</div>
  <!-- BEGIN Offer itemscope -->
  <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
    <meta itemprop="priceCurrency" content="{{ shop.currency }}" />
    <meta itemprop="price" content="{{ product.selected_or_first_available_variant.price | money_without_currency | remove: ',' }}" />
    <meta itemprop="itemCondition" itemtype="http://schema.org/OfferItemCondition" content="http://schema.org/NewCondition"/>
    {% if product.selected_or_first_available_variant.available %}
      <link itemprop="availability" href="http://schema.org/InStock" />
    {% else %}
      <link itemprop="availability" href="http://schema.org/OutOfStock" />
    {% endif %}
  </div>
</div>

Final Steps

  1. Save and test your new product page in Google’s Structured Data Testing Tool, and fix all errors if any.
  2. Wait and check back periodically in Google Merchant center to ensure no new errors were introduced. It will take a few weeks before the “unable to update” warning disappears.

Related Reading

Categories
Facebook Ads Shopify

DIY Facebook Product Feed for Shopify

WARNING! This code is no longer maintained. Although the code in this post should still work, please use at your own risk. I recommend using the Shopify Facebook Marketing App to sync your product catalog.

Below is a free customizable DIY solution to create a Facebook Product Feed in Shopify.

  1. This is an advanced topic and assumes you have the required understanding of HTML/XML/Liquid, the Shopify Store Admin and Facebook Business Manager.
  2. There are several existing paid apps that allow you to do this without coding (Flexify and DataFeedWatch) and a free app (Facebook Marketing App by Shopify).

1. Install the Google Shopping Channel

Install Shopify’s free Google Shopping app. This will allow you to configure product properties such as Age Group, Gender, and Product Category.

2. Create an XML Collection Template

Create a custom collection template that will output your products as XML instead of HTML.

  • In the Shopify admin, go to Online Store > Themes > Action > Edit Code
  • Under Templates, choose Add a new Template
  • Choose collection from the drop down and name your template fb-product-feed

Paste the code below into your new template and click Save. (Best to copy the code from this link: Shopify Facebook Product Feed Template)

{% layout none %}<?xml version="1.0"?>
<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">
{%- paginate collection.products by 1000 -%}
{%- assign CountryCode = 'US' -%}
{%- if shop.currency == 'CAD' -%}{%- assign CountryCode = 'CA' -%}{%- endif -%}
{%- assign PriceAdjustment = 1.0 -%}
{%- assign PriceAdjustmentEffectiveDate =  '20181226T080000-0500/20190102T235900-0800' -%}

<channel>
<title>{{ shop.name }} {{ collection.title | replace: '&', '&' }}</title>
<link>{{ shop.url }}</link>
<description>{{ collection.description | strip_html }}</description>
{%- for product in collection.products -%} 
  {%- assign GoogleProductCategory = product.metafields.mm-google-shopping.google_product_category -%}
  {%- assign Gender = product.metafields.mm-google-shopping.gender -%}
  {%- assign AgeGroup = product.metafields.mm-google-shopping.age_group -%}
  {%- assign Color = "" -%}
  {%- assign Size = "" -%}

  {%- if product.variants.size > 0 -%}
  {%- for variant in product.variants -%}
    {%- for option in product.options -%}
  	  {%- if option == 'Color' -%}{% capture Color %}{{ variant.options[forloop.index0] }}{% endcapture %}
  	  {%- elsif option == 'Size' -%}{% capture Size %}{{ variant.options[forloop.index0] }}{% endcapture %}
  	  {%- endif -%}
    {%- endfor -%}

    {% comment %} Calculate Sales vs Base Pricing {% endcomment %} 
    {%- if variant.compare_at_price == blank -%}
      {%- assign BasePrice = variant.price -%}
    {%- else -%}
      {%- assign BasePrice = variant.compare_at_price -%}
    {%- endif -%}
    {%- assign SalePrice = variant.price | times: PriceAdjustment -%}

<item>
<title>{{ product.title | strip_html | strip_newlines | replace: '&', '&' }}{% unless product.title contains Color %} {{ Color | replace: '&', '&' }}{% endunless %}</title>
<link>{{ shop.url }}{{ variant.url }}</link>
<description>{{ product.title | strip_html | strip_newlines | replace: '&', '&' }} {{ variant.title | strip_html | strip_newlines | replace: '&', '&' }} {{ product.description | replace: '</', ' </' | strip_html | strip_newlines | replace: '&', '&' }}</description>
<g:google_product_category>{{ GoogleProductCategory | replace: '&', '&'  }}</g:google_product_category>
<g:item_group_id>{{ product.id }}</g:item_group_id>
<g:id>{{ variant.id }}</g:id>
<g:condition>new</g:condition>
<g:price>{{ BasePrice | money_without_currency }} {{ shop.currency }}</g:price>
{%- if SalePrice < BasePrice -%}<g:sale_price>{{ SalePrice  | money_without_currency }} {{ shop.currency }}</g:sale_price>{%-  endif -%}
{%- if PriceAdjustment < 1 -%}<g:sale_price_effective_date>{{ PriceAdjustmentEffectiveDate }}</g:sale_price_effective_date>{%- endif -%}
<g:availability>{% if variant.available %}in stock{% else %}out of stock{% endif %}</g:availability>
<g:image_link>http:{% if variant.image.src %}{{ variant.image.src | product_img_url: 'grande' }}{% else %}{{ product.featured_image.src | product_img_url: 'grande' }}{% endif %}</g:image_link>
<g:gtin>{{ variant.barcode }}</g:gtin>
<g:brand>{{ product.vendor }}</g:brand>
<g:mpn>{{ variant.sku }}</g:mpn>
<g:product_type>{{ product.type | replace: '&', '&' }}</g:product_type>
<g:age_group>{{ AgeGroup }}</g:age_group>
{% unless Color == "" %}<g:color>{{ Color | strip_html | strip_newlines | replace: '&', '&' }}</g:color>{% endunless %}
{% unless Size == "" %}<g:size>{{ Size | strip_html | strip_newlines | replace: '&', '&' }}</g:size><g:size_system>US</g:size_system>{% endunless %}
<g:gender>{{ Gender }}</g:gender>
<g:custom_label_0>{{ product.metafields.mm-google-shopping.custom_label_0 }}</g:custom_label_0>
<g:custom_label_1>{{ product.metafields.mm-google-shopping.custom_label_1 }}</g:custom_label_1>
<g:custom_label_2>{{ product.metafields.mm-google-shopping.custom_label_2 }}</g:custom_label_2>
<g:custom_label_3>{{ product.metafields.mm-google-shopping.custom_label_3 }}</g:custom_label_3>
<g:custom_label_4>{{ product.metafields.mm-google-shopping.custom_label_4 }}</g:custom_label_4>
<g:shipping_weight>{{ variant.weight | weight_with_unit }}</g:shipping_weight>
</item>

  {% endfor %}
  {% else %}

  {% comment %} Calculate Sales vs Base Pricing {% endcomment %} 
  {%- if product.compare_at_price_min == blank -%}
    {%- assign BasePrice = product.price -%}
  {%- else -%}
    {%- assign BasePrice = product.compare_at_price_min -%}
  {%- endif -%}
  {%- assign SalePrice = product.price | times: PriceAdjustment -%}

<item>
<title>{{ product.title | strip_html | strip_newlines | replace: '&', '&' }}</title>
<link>{{ shop.url }}{{ product.url }}</link>
<description>{{ product.title | strip_html | strip_newlines | replace: '&', '&' }} {{ product.description | replace: '</', ' </' | strip_html | strip_newlines | replace: '&', '&' }}</description>
<g:google_product_category>{{ GoogleProductCategory | replace: '&', '&'  }}</g:google_product_category>
<g:item_group_id>{{ product.id }}</g:item_group_id>
<g:id>{{ product.id }}</g:id>
<g:condition>new</g:condition>
<g:price>{{ BasePrice | money_without_currency }} {{ shop.currency }}</g:price>
{%- if SalePrice < BasePrice -%}<g:sale_price>{{ SalePrice  | money_without_currency }} {{ shop.currency }}</g:sale_price>{%-  endif -%}
{%- if PriceAdjustment < 1 -%}<g:sale_price_effective_date>{{ PriceAdjustmentEffectiveDate }}</g:sale_price_effective_date>{%- endif -%}
<g:availability>{% if product.available %}in stock{% else %}out of stock{% endif %}</g:availability>
<g:image_link>http:{{ product.featured_image.src | product_img_url: 'grande' }}</g:image_link>
<g:gtin>{{ product.barcode }}</g:gtin>
<g:brand>{{ product.vendor }}</g:brand>
<g:mpn>{{ product.sku }}</g:mpn>
<g:product_type>{{ product.type }}</g:product_type>
<g:age_group>{{ AgeGroup }}</g:age_group>
<g:gender>{{ Gender }}</g:gender>
<g:custom_label_0>{{ product.metafields.mm-google-shopping.custom_label_0 }}</g:custom_label_0>
<g:custom_label_1>{{ product.metafields.mm-google-shopping.custom_label_1 }}</g:custom_label_1>
<g:custom_label_2>{{ product.metafields.mm-google-shopping.custom_label_2 }}</g:custom_label_2>
<g:custom_label_3>{{ product.metafields.mm-google-shopping.custom_label_3 }}</g:custom_label_3>
<g:custom_label_4>{{ product.metafields.mm-google-shopping.custom_label_4 }}</g:custom_label_4>
<g:shipping_weight>{{ variant.weight | weight_with_unit }}</g:shipping_weight>
</item>
  {% endif %}
{% endfor %}
</channel>
</rss>
{% endpaginate %}

or Download from Github:  Shopify Facebook Product Feed Template

3. Assign products to your Feed

In Step 2 you created your feed template. Now you need to assign products to this feed:

  • In Shopify Admin, go to Products > Collections > Create Collection
  • Enter a Title: “Facebook Product Feed”
  • Add Products to the collection (either manually or using conditions)
  • IMPORTANT! Assign your feed TEMPLATE to this collection.
    In the bottom right column choose collection.fb-product-feed as the Theme Template.
  • Save and Preview the collection. You should see unformatted text on the screen. This is your Facebook feed.
  • Copy the url as you need it in the next step.

4. Upload your Feed to Facebook

  • In Facebook Business Manager go to Assets > Catalogs > Create Catalog.
  • Catalog Type: E-Commerce
  • Click Add ProductsUse Datafeed
  • Enter the feed collection url you copied in step 3 above. Leave the username & password blank. Choose a time for your daily upload to occur (early morning is usually a good time). Choose your currency.
  • Click Start Upload and wait for the feed to be fetched and processed.
  • Fix errors: If there are errors, go back, fix them, re-fetch, and keep doing so until the feed is error free. Sometimes it is necessary to delete and re-create your catalog in Facebook for some changes to appear.
  • If you have more than 1000 product variants, you will need to submit multiple feeds with a ?page=x querystring appended like so: http://mystore.myshopify.com/collections/facebook-product-feed?page=1 (This will send products 1-1000) and http://mystore.myshopify.com/collections/facebook-product-feed?page=2 (This will send products 1001-2000)

5. Prevent the Facebook Feed from Showing on your Store

Depending on how your store is setup, you may need to add some code to prevent your Facebook feed collection from showing up on your store. The exact way to do this may depend on your theme, but generally you will want to have an “unless” statement within the loop that displays your collections:

{% unless collection.title contains "Facebook" %}
... your collection code ...
{% endunless %}

Done!

You are now ready to setup your Dynamic Product Remarketing campaigns!

Related Posts

Categories
Analytics Shopify

Fix Product Performance Reports in Google Analytics with Shopify

By default, Shopify sends transactions to Google Analytics with a unique product title for each product variant. This causes the Product Performance Report to be split on the variant level instead of at the product level as it was intended.

ga-productperformance-productname
This is how Shopify data shows up by default in the Product Performance Report. Notice that the various “Trillium Parka” variants are ungrouped because of the different size and color information in the product name. This makes it difficult to see “Revenue by Product” for all “Trillium Parkas”.

If, for example. you are selling winter boots, and someone buys a size 10 in Black, Shopify will send the product name as “Winter Boots – 10 / Black” instead of just “Winter Boots“. This is a bug as as the variant details are already included in the Google Analytics “Product Variant” column.

The Solution: GA Custom Data Import

The solution to this problem is to overwrite the Shopify data using the Google Analytics Custom Data Import tool.

1: Export your Product Data

First we need to export all our product data – we can accomplish this by creating a custom Collection Template that generates a CSV report instead of the standard HTML.

a. Create a new Collection Template

Call the new collection template csv-ga-product-feed and paste the following code:

{% layout none %}{% paginate collection.products by 1000 %}ga:productSku,ga:productName,ga:productVariant{% for product in collection.products %}{% for variant in product.variants %}
{{ variant.sku }},{{ product.title | replace: ',','' | remove: '"' | remove: "'" | strip_html | strip }},{{variant.title | replace: ',','' | remove: '"' | remove: "'" | strip_html | strip }}{% endfor %}{% endfor %}{% endpaginate %}

(also available on GitHub here)

b. Create a new Collection based on your csv-ga-product-feed Template

Select the products you want to include in this feed (probably all your products). These will be the products whose values will be overwritten in Google Analytics. Call your collection “Google Analytics Product Data Import” or something similar and save it.

c. Download your Product Feed

  • View your new collection in your store (eg: store.myshopify.com/collections/google-analytics-product-data-import)
  • View source in your browser and save as HTML
  • Rename the file with a CSV extension (eg: google-analytics-product-data-import.csv)

2: Setup and Import the data into Google Analytics

WARNING! You can really mess up your Google Analytics data if things go wrong. I highly recommend that you duplicate or backup your Google Analytics view and do a trial run before working with your live data. Once you upload this new data and overwrite there is no UNDO!

a. Setup the Data Feed

  • Go to Google Analytics > Admin > Account > Property > Data Import
  • Click the red “+ NEW DATA SET” button
  • Select “Product Data”
  • Give your Data Import a name: “Product Name Override”
  • Select the Google Analytics Views you want this import to affect
  • Setup your Data Set Schema: Product SKU is the mandatory key, but select Product and Product Variant as the additional fields.
  • Overwrite Hit Data: Choose Yes (but read my warning above)
  • Click Save and Done

b. Upload your data feed

  • Click on “manage uploads” beside your new Data Feed definition
  • Click the blue UPLOAD button
  • Choose your CSV file and click UPLOAD again
  • And now wait for the upload an update to be complete

3: Verify your new data

The data upload will only affect data from this date forward. So your old data will not be fixed. But your future data will be nice and clean… Until you add new products to your store, in which case you will have to repeat this process.

You will need to wait at least a day before you start seeing the new data coming in. If you add new product SKUs to your store, you will also need to regenerate and reupload a new file in order for the new product data to be fixed.

Categories
eCommerce Google Ads Shopify Shopping

DIY Shopify xml Product Feed for Google Shopping

WARNING: This post is OUTDATED and the code is no longer maintained. Although the code should still work, please use at your own risk. I personally now use the Google Sales Channel App in combination with Merchant Center feed rules to manage my Shopping Campaigns.

The custom XML Product Feed Code is still available here: Shopify XML Product Feed Template


Here is how to build your own Shopify XML product feed to submit to Google Shopping or other platforms.

  1. Install and upload your feed using the Shopify Google Shopping App (This will setup some meta fields that are used in the script)
  2. Create a custom collection template that outputs xml
  3. Assign the products you want to publish to your new xml collection template.
  4. Submit your new xml collection url to Google Merchant Center.

Step 1: Install Shopify’s Google Shopping App

Shopify’s app is an excellent tool for managing your Google Shopping product attributes, including custom labels, product categories, etc… The custom feed we are about to build will enhance Shopify’s base feed with extra variant information and customized titles. But the base app is still needed to make use of the app’s metafields.

Step 2: Create the XML Collection Template

This step involves getting your hands dirty with code. If you are not a developer and have a low appetite for risk, consider asking a coder to do this step for you.

What we are doing here is creating a template, that when assigned to a collection, will cause that collection to be displayed as XML instead of HTML.

In your Shopify admin, go to:

  • Online Store > Themes > Edit HTML / CSS
  • Under Templates, choose Add a new Template
  • Choose collection from the drop down and name your template xml-product-feed

Paste the following code into your new template and click Save. (It is probably safer to copy and paste this code from github, as that will always be the most up to date version: Shopify xml Variant Shopping Feed)

IMPORTANT: Do not add any extra spaces before or after this code. Your very first line should be start with {% layout none %} and your very last line should be {% endpaginate %}

{% layout none %}<?xml version="1.0"?> <rss xmlns:g="http://base.google.com/ns/1.0" version="2.0"> {% paginate collection.products by 1000 %} {%- assign useSEOtitle = false -%} {%- assign useSEOdescription = false -%} {%- assign CountryCode = 'US' -%} {%- if shop.currency == 'CAD' -%}{%- assign CountryCode = 'CA' -%}{%- endif -%} {%- assign Color = "" -%} {%- assign Size = "" -%} <channel> <title>{{ shop.name }} {{ collection.title | strip_html | strip_newlines | replace: '&', '&amp;' }}</title> <link>{{ shop.url }}</link> <description>{{ collection.description | strip_html | strip_newlines | replace: '&', '&amp;' }}</description> {% for product in collection.products %}  {%- assign GoogleProductCategory = product.metafields.mm-google-shopping.google_product_category -%} {%- assign Gender = product.metafields.mm-google-shopping.gender -%} {%- assign AgeGroup = product.metafields.mm-google-shopping.age_group -%}          {% for variant in product.variants %}     {%- assign Color = "" -%}     {%- assign Size = "" -%}     {%- for option in product.options -%}    {%- if option == 'Color' -%}{%- capture Color -%}{{ variant.options[forloop.index0] }}{%- endcapture -%}    {%- elsif option == 'Size' -%}{%- capture Size -%}{{ variant.options[forloop.index0]  }}{%- endcapture -%}    {%- endif -%}    {%- endfor -%}          {%- capture productTitle -%}{{ product.vendor }} {{ product.title }}{%- endcapture -%}     {%- unless productTitle contains Color -%}{%- capture productTitle -%}{{ productTitle }} {{ Color }}{%- endcapture -%}{%- endunless -%}     {%- if useSEOtitle and product.metafields.global.title_tag.size > 0 -%}{%- assign productTitle = product.metafields.global.title_tag -%}{%- endif -%}     {%- assign productDescription = product.description -%}     {%- if useSEOdescription and product.metafields.global.description_tag.size > 0 -%}{%- assign productDescription = product.metafields.global.description_tag -%}{%- endif -%}     {%- assign OnSale = false -%}     {%- assign Price = variant.price -%}     {%- if variant.compare_at_price > variant.price -%}       {%- assign OnSale = true -%}       {%- assign Price = variant.compare_at_price -%}       {%- assign SalePrice = variant.price -%}     {%- endif -%} <item> <title>{{ productTitle | strip_html | replace: '&', '&amp;' }}</title> <link>{{ shop.url }}{{ variant.url }}</link> <description>{{ productDescription | strip_html | strip_newlines | replace: '&', '&amp;' }}</description> <g:google_product_category>{{ GoogleProductCategory | replace: '&', '&amp;'  }}</g:google_product_category> <g:item_group_id>shopify_{{ CountryCode }}_{{ product.id }}</g:item_group_id> <g:id>shopify_{{ CountryCode }}_{{ product.id }}_{{ variant.id }}</g:id> <g:condition>new</g:condition> <g:price>{{ Price | money_without_currency }} {{ shop.currency }}</g:price> {%- if OnSale -%} <g:sale_price>{{ SalePrice | money_without_currency }} {{ shop.currency }}</g:sale_price> {%- endif -%} <g:availability>{% if variant.available %}in stock{% else %}out of stock{% endif %}</g:availability> <g:image_link>http:{% if variant.image.src %}{{ variant.image.src | product_img_url: 'grande' }}{% else %}{{ product.featured_image.src | product_img_url: 'grande' }}{% endif %}</g:image_link> <g:gtin>{{ variant.barcode }}</g:gtin> <g:brand>{{ product.vendor }}</g:brand> <g:mpn>{{ variant.sku }}</g:mpn> <g:product_type>{{ product.type }}</g:product_type> <g:age_group>{{ AgeGroup }}</g:age_group> {% unless Color == "" %}<g:color>{{ Color | strip_html | strip_newlines | replace: '&', '&amp;' }}</g:color>{% endunless %} {%- unless Size == "" -%} <g:size>{{ Size | strip_html | strip_newlines | replace: '&', '&amp;' }}</g:size> <g:size_system>US</g:size_system> {%- endunless -%} <g:gender>{{ Gender }}</g:gender> <g:custom_label_0>{{ product.metafields.mm-google-shopping.custom_label_0 }}</g:custom_label_0> <g:custom_label_1>{{ product.metafields.mm-google-shopping.custom_label_1 }}</g:custom_label_1> <g:custom_label_2>{{ product.metafields.mm-google-shopping.custom_label_2 }}</g:custom_label_2> <g:custom_label_3>{{ product.metafields.mm-google-shopping.custom_label_3 }}</g:custom_label_3> <g:custom_label_4>{{ product.metafields.mm-google-shopping.custom_label_4 }}</g:custom_label_4> <g:shipping_weight>{{ variant.weight | weight_with_unit }}</g:shipping_weight> </item> {% endfor %} {% endfor %} </channel> </rss> {% endpaginate %}

or Download from Github: Shopify xml Product Feed

Step 3: Assign products to your xml Collection.

You should now have a new XML Template created. This is just the template that defines how your collection will be displayed on your site. We now need to assign products to this template so we can submit the feed to google.

This involves creating a new product collection, and choosing the xml-product-feed as the template to use:

  • Go to Products > Collections
  • Click the Add Collection button
  • Enter a name for your new collection. I recommend XML Google Shopping Feed – All if you want a single feed for all your products or XML Google Shopping Feed – Shoes if you want this feed to contain your shoe products (or other product type).
  • Add products you want published as part of this collection feed. (either manually or via dynamic rules)
  • Click Save.

Preview the collection to make sure it works. You should just see a bunch of unformatted text on the screen. If you do a “view source” in your browser, you should now see the formatted XML.

Make sure you copy the url of this collection, as you will need to submit this url to Google Merchant center.

Step 4: Submit your feed to Google Merchant

Make sure you have your xml collection url open from step 3 above.

  • Log into Google Merchant Center or create an account if you do not have one.
  • Go to Feeds and click the +Feed button to create a new feed.
  • Mode: Choose Standard or Test (recommended that you choose Test until you are sure the feed is correct)
  • Feed type: Products
  • Select your target country & language. If you have multiple target countries, you need to submit a separate feed for each.
  • Enter a descriptive name for your feed. eg: Shopify XML Feed – Shoes
  • Choose Scheduled Fetches as your input method (recommend daily)
  • Enter your collection url in the File URL field, and the collection’s filename in the name field (eg: name=google-shopping-feed-shoes and url=http://mystore.myshopify.com/collections/google-shopping-feed-shoes)
  • Save and click the Fetch Now button to download the feed. Wait a few minutes for the feed to complete.
  • Fix errors: If there are errors, Google Merchant Center will tell you in a few minutes. Go back and fix any errors, re-fetch, and keep doing so until the file is error free.

If you have more than 1000 variants, you will need to submit multiple feeds with the ?page=x querystring appended as so:

Troubleshooting

If you receive any errors, the best way to troubleshoot them is to open the the collection / feed url in your browser and do a view source. Then navigate to the line & character of the error to investigate further

Common Errors

  • Improperly formatted XML Error on Line 1: This is usually because your template has a blank line as it’s first line. Delete the blank line and make sure your template begins with {% layout none %} … exactly as in the code.
  • Improperly formatted XML Error on Line 1: The other cause of this error is that you forgot to choose the XML template for your Shopping Feed Url, and you are actually outputting HTML. Go to your collection in the Shopify Admin console and set the template correctly (at the bottom of the right hand column) 
  • Errors at Lines XXX: The xml file does not support & and > characters. These must be replaced by encoded versions &amp ; &gt ; or the feed will report errors.

(optional) Step 5: Exclude your feed collection from showing up on your store pages

If your store is setup to programatically display all your collections, then you will want to make sure your xml feed doesn’t show up. The exact way to do this will change depending on your theme, but generally you will want to have an “unless” statement within the loop that displays your collections:

{% unless collection.title contains "xml" %}
... your collection code ...
{% endunless %}

Customizing

If you have complex rules or customization that you want to apply to your feed, you have several options:

  • Use the Google Merchant Center “Feed Rules” tab to transform various values based on various conditions. This is very powerful!
  • Modify your collection template and add conditional statements based on product titles, tags, metafields, etc…
  • Create multiple collection templates with hard coded values
  • Create custom Feed Rules in Google Merchant Center
  • A combination of the above.

If you have more than 1000 variants, you will need to submit multiple feeds with the ?page=x querystring appended as so:


Related Posts & Resources

Categories
Shopify

Shopify Cancelled Orders and Google Analytics

DEPRECATED: This code is no longer supported. Although it probably still works, please use at your own risk!

By default, when you cancel an order in Shopify, that transaction remains as positive revenue in your Google Analytics.

To “cancel” the transaction in Google Analytics you have to send a negated version of the transaction. To do this in Shopify you have to create a Webhook on Order Cancelled that hits a script (located on the same root domain as your store) that will call server side Google Analytics e-commerce code to negate the transaction.

Webhook Endpoint Dependencies

Details

Place the following code into a file that will act as your Order Cancelled Webhook endpoint (ie: www2.mydomain.com/webhooks/order-cancelled.php).

Make sure you:

  • Update the script to use your GA Account Id and Root Domain.
  • Change the path of autoload.php to point at your php-ga library
<?php
use UnitedPrototype\GoogleAnalytics;
require_once '../includes/autoload.php'; // Update to point at your php-ga install

$GA_AccountId = 'UA-********-1'; // Update with your GA account
$GA_domain = 'mystore.ca'; // Update with your root domain
$webhookContent = '';
// Read the webhook content
$webhook = fopen('php://input' , 'rb');
while (!feof($webhook)) {
  $webhookContent .= fread($webhook, 4096);
}
fclose($webhook);

if (!empty($webhookContent)) {
  // Convert the webhook content into an array
  $shopifyOrder = json_decode($webhookContent, true);

  // START GOOGLE ANALYTICS
  $tracker = new GoogleAnalytics\Tracker($GA_AccountId, $GA_domain);

  $visitor = new GoogleAnalytics\Visitor();
  $visitor->setIpAddress($_SERVER['REMOTE_ADDR']);
  $visitor->setUserAgent($_SERVER['HTTP_USER_AGENT']);
  $visitor->setScreenResolution('1024x768');

  $session = new GoogleAnalytics\Session();

  $page = new GoogleAnalytics\Page($_SERVER['REQUEST_URI']);
  $page->setTitle('Order Cancelled');

  $tracker->trackPageview($page, $session, $visitor);

  $transaction = new GoogleAnalytics\Transaction();
  $transaction->setOrderId($shopifyOrder['name']);
  $transaction->setAffiliation('');
  $transaction->setTotal(-$shopifyOrder['total_price']);
  $transaction->setTax(-$shopifyOrder['total_tax']);
  $transaction->setShipping(-$shopifyOrder['shipping_lines'][0]['price']);
  $transaction->setCity($shopifyOrder['billing_address']['city']);
  $transaction->setRegion($shopifyOrder['billing_address']['province']);
  $transaction->setCountry($shopifyOrder['billing_address']['country']);

  foreach ( $shopifyOrder['line_items'] as $product ) {
    $item = new GoogleAnalytics\Item();
    $item->setOrderId($shopifyOrder['name']);
    $item->setSku($product['sku']);
    $item->setName($product['title']);
    $item->setVariation('');
    $item->setPrice($product['price']);
    $item->setQuantity(-$product['quantity']);
    $item->validate();
    $transaction->addItem($item);
  }
  $transaction->validate(); 

  $tracker->trackTransaction($transaction, $session, $visitor);
  // END GOOGLE ANALYTICS
}
?>

Setup the Webhook in Shopify

In your Admin dashboard go to:

  • Settings > Notifications > Webhooks (at the bottom)
  • Create a Webhook
  • Event: Order Cancellation // Format: JSON // URL: The full url of your php file

Test

  • Google Analytics: Got to the Real Time > Content report
  • Shopify: Click “Send test notification” link beside your webhook.
  • Google Analytics: You should see a page request popup with your script name
  • Google Analytics: Wait a few hours and then (for transactions to register) and then go to Conversions > Ecommerce > Product Performance and you should see a sledge-hammer and wire-cutter products (the Shopify sample data) along with negative quantities and value.

Related Resources

Categories
Attribution Digital Marketing Facebook Ads Google Ads

The Value of View Through Conversions

My personal experience with View Through Conversions (VTCs) is that they provide very little real-world lift in sales, and are more likely just poaching attribution from other channels. So I almost always give VTCs a value of zero and ignore them.

The truth probably lies somewhere in-between. For lower funnel remarketing campaigns VTCs probably provide close to zero value, while for truly brand-unaware audiences, the value is somewhere between 0 and 100%.

If you are one of those who wants to believe in the value of VTCs, you should calculate the true value of VTCs by running a placebo A/B test. Below is just such a test that we ran on the AdRoll network back in 2013:

Placebo A/B Tests to Measure View Through Conversions

Display Network A/B Test

How to Setup a Placebo A/B Test

You will likely need assistance from your ad platform or ad agency to properly run this sort of test. It is usually difficult to have a blank or psa ad approved.

  1. Create 2 separate (but equal) non-overlapping campaigns
  2. Campaign A will serve your “normal” ad
    Campaign B will serve a Public Service Announcement ad
  3. Run both campaigns for a few weeks
  4. Calculate VTC “lift” as follows:
Valid VTC Formula

Real Life Example

Campaign A
(Real Ad)
Campaign B
(PSA Ad)
Impressions99,46797,412
VTCs329261
Data from a real world A/B test performed in 2013 on the AdRoll network

VTC “Lift” = (329 – 261) / 329 = 20%

In this real life example, the irrelevant PSA Ads still managed to generate 80% of the View Through Conversion volume that the real ad generated. To be more clear, 261 people saw an ad to adopt a cat, and later went to BOATERexam.com to purchase a boating license.

So 80% of VTCs can be given a value of zero. Of the remaining 20%, more analysis is needed to figure out exactly how they influenced sales. The Real Ad did seem to generate more VTCs than the PSA ad, but the real question is if those VTCs resulted in extra incremental sales or were they simply tracking sales generated by another channel such as e-mail?

Real Life Example #2: Facebook

A few years later we ran a similar Placebo A/B test on Facebook with the help of SocialCode. Unfortunately I no longer have the data for this test, but the end results was that there was ZERO lift from VTCs, and the PSA ad actually outperformed the real ads from a VTC standpoint! (People seeing cats were buying more boating licenses than the people seeing ads for a boating license)

So how should you value View Through Conversions?

Here are my recommendations:

  • Give View Through Conversions a value of ZERO. Unless you can prove otherwise via an A/B test. This is especially true for re-marketing campaigns where the visitors have previously visited your site, and may be actively engaged in checkout when the ad is shown.
  • Run your own Placebo A/B test. If someone insists on using VTCs in performance metrics, then you should insist on running an A/B test to calculate the true value. You should run at least two tests: One for remarketing audiences and another for brand-unaware audiences.
  • Be cautious and skeptical of anyone pushing the value of VTCs especially if they are an ad agency or ad platform that will benefit from including the extra VTCs in their performance metrics.