Promotion & Coupon Tracking | Blue Frog Docs

Promotion & Coupon Tracking

Diagnosing and fixing promotion, coupon, and discount tracking issues to measure campaign effectiveness and revenue impact.

Promotion & Coupon Tracking

What This Means

Promotion and coupon tracking issues occur when GA4 doesn't properly record promotional banners, discount codes, and special offers used on your site. Without accurate promotion data, you can't measure ROI of marketing campaigns, understand which offers drive conversions, or optimize your promotional strategy.

Key Promotion Events:

  • view_promotion - Promotional banner/offer is viewed
  • select_promotion - User clicks on a promotion
  • add_to_cart, begin_checkout, purchase - Include coupon parameter when discount applied

Impact Assessment

Business Impact

  • Campaign ROI Unknown: Can't measure which promotions drive revenue
  • Budget Waste: Spending on ineffective promotions without knowing
  • Coupon Abuse: Can't track coupon usage patterns or detect fraud
  • Lost Attribution: Don't know which offers influenced purchases

Analytics Impact

  • Incomplete Revenue Data: Can't separate discounted vs. full-price sales
  • Missing Promotion Reports: GA4 promotion reports are empty
  • Attribution Gaps: Can't credit promotions for conversions
  • Poor Segmentation: Can't analyze customer behavior by promotion type

Common Causes

Implementation Issues

  • view_promotion event not implemented
  • Coupon codes not passed to e-commerce events
  • Promotion creative_name and creative_slot missing
  • Events fire without promotion_id or promotion_name

Technical Problems

  • Coupon applied but not captured in data layer
  • Third-party coupon tools bypass tracking
  • Auto-applied discounts not tracked
  • Multiple coupons combined but only one tracked

Data Quality Issues

  • Coupon codes inconsistent (uppercase/lowercase)
  • Free shipping treated as coupon code
  • Promotion names not standardized
  • Creative slots not uniquely identified

How to Diagnose

Check for Promotion Events

// Monitor all promotion events
const promoEvents = ['view_promotion', 'select_promotion'];
const trackedPromotions = new Set();

window.dataLayer = window.dataLayer || [];
const originalPush = dataLayer.push;

dataLayer.push = function(...args) {
  args.forEach(event => {
    // Check promotion-specific events
    if (promoEvents.includes(event.event)) {
      console.log('๐ŸŽ Promotion Event:', event.event, event.ecommerce);

      const items = event.ecommerce?.items || [];
      items.forEach(promo => {
        const key = `${promo.promotion_id}_${promo.creative_slot}`;
        if (trackedPromotions.has(key)) {
          console.warn('โš ๏ธ Duplicate promotion:', promo.promotion_name);
        }
        trackedPromotions.add(key);
      });
    }

    // Check for coupon in e-commerce events
    if (event.ecommerce?.coupon) {
      console.log('๐ŸŽŸ๏ธ Coupon Applied:', {
        event: event.event,
        coupon: event.ecommerce.coupon,
        value: event.ecommerce.value
      });
    }
  });
  return originalPush.apply(this, args);
};

Validate Coupon Tracking

// Test if coupons are being tracked properly
function validateCouponTracking() {
  // Check cart/checkout events for coupon parameter
  const ecomEvents = dataLayer.filter(e =>
    ['add_to_cart', 'begin_checkout', 'add_payment_info', 'purchase'].includes(e.event)
  );

  const withCoupon = ecomEvents.filter(e => e.ecommerce?.coupon);
  const withoutCoupon = ecomEvents.filter(e => !e.ecommerce?.coupon);

  console.log('E-commerce events:', ecomEvents.length);
  console.log('With coupon:', withCoupon.length);
  console.log('Without coupon:', withoutCoupon.length);

  // Check if active coupon exists on page
  const activeCoupon = document.querySelector('.coupon-code')?.textContent ||
                       document.querySelector('[data-coupon-code]')?.dataset.couponCode;

  if (activeCoupon && withoutCoupon.length > 0) {
    console.error('โš ๏ธ Coupon applied but not tracked in events!');
    console.log('Active coupon:', activeCoupon);
  }
}

validateCouponTracking();

GA4 DebugView Checklist

  1. Banner Impressions: view_promotion fires when promo visible
  2. Banner Clicks: select_promotion fires on click
  3. Coupon Usage: E-commerce events include coupon parameter
  4. Required Fields: All promotions have promotion_id and promotion_name
  5. Creative Attribution: creative_name and creative_slot present

General Fixes

1. Track Promotion Impressions

// Track when promotional banner is viewed
function trackViewPromotion(promotions) {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'view_promotion',
    ecommerce: {
      items: promotions.map(promo => ({
        promotion_id: promo.id,
        promotion_name: promo.name,
        creative_name: promo.creativeName,
        creative_slot: promo.slot,
        location_id: promo.location
      }))
    }
  });
}

// Track banners when they enter viewport
const promoObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting && !entry.target.dataset.tracked) {
      entry.target.dataset.tracked = 'true';

      const promotion = {
        id: entry.target.dataset.promoId,
        name: entry.target.dataset.promoName,
        creativeName: entry.target.dataset.creativeName,
        slot: entry.target.dataset.slot,
        location: entry.target.dataset.location
      };

      trackViewPromotion([promotion]);
    }
  });
}, {
  threshold: 0.5
});

// Observe all promotional banners
document.querySelectorAll('[data-promotion]').forEach(banner => {
  promoObserver.observe(banner);
});

2. Track Promotion Clicks

// Track when user clicks a promotional banner
function trackSelectPromotion(promotion) {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'select_promotion',
    ecommerce: {
      items: [{
        promotion_id: promotion.id,
        promotion_name: promotion.name,
        creative_name: promotion.creativeName,
        creative_slot: promotion.slot,
        location_id: promotion.location
      }]
    }
  });
}

// Attach to all promotion links/buttons
document.querySelectorAll('[data-promotion]').forEach(element => {
  element.addEventListener('click', (e) => {
    const promotion = {
      id: element.dataset.promoId,
      name: element.dataset.promoName,
      creativeName: element.dataset.creativeName,
      slot: element.dataset.slot,
      location: element.dataset.location
    };

    trackSelectPromotion(promotion);
  });
});

3. Track Coupon Application

// Track when coupon is applied to cart
function trackCouponApplication(couponCode, discountAmount) {
  // Store coupon for use in other e-commerce events
  sessionStorage.setItem('applied_coupon', couponCode);
  sessionStorage.setItem('discount_amount', discountAmount);

  // Fire custom event
  dataLayer.push({
    event: 'coupon_applied',
    coupon_code: couponCode,
    discount_amount: discountAmount
  });

  console.log('โœ“ Coupon applied:', couponCode);
}

// Listen for coupon form submission
document.querySelector('#coupon-form')?.addEventListener('submit', async (e) => {
  e.preventDefault();

  const couponCode = document.querySelector('#coupon-code').value.trim().toUpperCase();

  // Apply coupon via API
  const response = await fetch('/api/apply-coupon', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code: couponCode })
  });

  const result = await response.json();

  if (result.success) {
    trackCouponApplication(couponCode, result.discountAmount);
    updateCartDisplay();
  } else {
    console.error('Coupon failed:', result.error);
  }
});

4. Include Coupon in E-commerce Events

// Helper to get current coupon
function getCurrentCoupon() {
  return sessionStorage.getItem('applied_coupon') || '';
}

// Include coupon in all e-commerce events
function trackAddToCart(product, quantity) {
  const coupon = getCurrentCoupon();

  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'add_to_cart',
    ecommerce: {
      currency: 'USD',
      value: product.price * quantity,
      coupon: coupon, // Include coupon
      items: [{
        item_id: product.id,
        item_name: product.name,
        price: product.price,
        quantity: quantity,
        coupon: coupon // Also at item level
      }]
    }
  });
}

function trackBeginCheckout(cartData) {
  const coupon = getCurrentCoupon();

  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'begin_checkout',
    ecommerce: {
      currency: 'USD',
      value: cartData.total,
      coupon: coupon,
      items: cartData.items.map(item => ({
        ...item,
        coupon: coupon
      }))
    }
  });
}

function trackPurchase(orderData) {
  const coupon = getCurrentCoupon();

  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'purchase',
    ecommerce: {
      transaction_id: orderData.id,
      value: orderData.total,
      tax: orderData.tax,
      shipping: orderData.shipping,
      currency: 'USD',
      coupon: coupon,
      items: orderData.items.map(item => ({
        ...item,
        coupon: coupon
      }))
    }
  });

  // Clear coupon after purchase
  sessionStorage.removeItem('applied_coupon');
  sessionStorage.removeItem('discount_amount');
}

5. Track Multiple Coupons

// Handle multiple coupons applied to single order
function trackMultipleCoupons(coupons) {
  // GA4 only accepts single coupon string
  // Concatenate multiple coupons with delimiter
  const couponString = coupons.join(',');

  sessionStorage.setItem('applied_coupons', couponString);

  // Also track each coupon individually in custom event
  dataLayer.push({
    event: 'multiple_coupons_applied',
    coupons: coupons,
    total_discount: coupons.reduce((sum, c) => sum + c.amount, 0)
  });
}

// Use in e-commerce events
function getAppliedCoupons() {
  return sessionStorage.getItem('applied_coupons') || '';
}

6. Track Auto-Applied Discounts

// Track automatic discounts (no coupon code needed)
function trackAutoDiscount(discountInfo) {
  // Use special code for auto discounts
  const couponCode = `AUTO_${discountInfo.type.toUpperCase()}`;

  sessionStorage.setItem('applied_coupon', couponCode);

  dataLayer.push({
    event: 'auto_discount_applied',
    discount_type: discountInfo.type,
    discount_name: discountInfo.name,
    discount_amount: discountInfo.amount,
    auto_discount_code: couponCode
  });
}

// Example: Free shipping over $50
if (cartTotal >= 50) {
  trackAutoDiscount({
    type: 'free_shipping',
    name: 'Free Shipping Over $50',
    amount: 0 // or calculated shipping cost
  });
}

// Example: Buy 2 Get 1 Free
if (hasBogo) {
  trackAutoDiscount({
    type: 'bogo',
    name: 'Buy 2 Get 1 Free',
    amount: bogoDiscountAmount
  });
}

7. Standardize Promotion Naming

// Create consistent promotion naming convention
const PROMOTION_TYPES = {
  banner: {
    homepage_hero: (name) => ({
      id: `banner_homepage_hero`,
      name: name,
      creativeName: name,
      slot: 'homepage_hero',
      location: 'homepage'
    }),
    sidebar: (name, page) => ({
      id: `banner_${page}_sidebar`,
      name: name,
      creativeName: name,
      slot: 'sidebar',
      location: page
    }),
    popup: (name) => ({
      id: `popup_${name.toLowerCase().replace(/\s+/g, '_')}`,
      name: name,
      creativeName: name,
      slot: 'popup',
      location: 'sitewide'
    })
  },
  coupon: {
    percentage: (code, percent) => `${code}_${percent}OFF`,
    fixed: (code, amount) => `${code}_$${amount}OFF`,
    freeShipping: (code) => `${code}_FREESHIP`,
    bogo: (code) => `${code}_BOGO`
  }
};

// Usage
const heroPromo = PROMOTION_TYPES.banner.homepage_hero('Summer Sale 2025');
trackViewPromotion([heroPromo]);

const couponCode = PROMOTION_TYPES.coupon.percentage('SUMMER25', 25);
trackCouponApplication(couponCode, discountAmount);

Platform-Specific Guides

Shopify

<!-- Theme.liquid - Track promotional banners -->
<script>
document.addEventListener('DOMContentLoaded', function() {
  // Track homepage hero banner
  {% if template == 'index' and section.settings.hero_promo_enabled %}
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'view_promotion',
    ecommerce: {
      items: [{
        promotion_id: 'banner_homepage_hero',
        promotion_name: '{{ section.settings.hero_promo_text | escape }}',
        creative_name: '{{ section.settings.hero_promo_text | escape }}',
        creative_slot: 'homepage_hero',
        location_id: 'homepage'
      }]
    }
  });
  {% endif %}

  // Track promotion banner clicks
  document.querySelectorAll('[data-promotion-banner]').forEach(banner => {
    banner.addEventListener('click', function() {
      dataLayer.push({ ecommerce: null });
      dataLayer.push({
        event: 'select_promotion',
        ecommerce: {
          items: [{
            promotion_id: this.dataset.promoId,
            promotion_name: this.dataset.promoName,
            creative_name: this.dataset.creativeName,
            creative_slot: this.dataset.slot,
            location_id: window.location.pathname
          }]
        }
      });
    });
  });
});

// Track discount code application
(function() {
  const originalFetch = window.fetch;
  window.fetch = function(...args) {
    const [url, options] = args;

    // Intercept discount code API calls
    if (url.includes('/discount/') || url.includes('/cart/update')) {
      return originalFetch.apply(this, args).then(response => {
        return response.clone().json().then(data => {
          // Check if discount was applied
          if (data.cart && data.cart.total_discount > 0) {
            const discountCode = document.querySelector('[name="discount"]')?.value ||
                               data.cart.cart_level_discount_applications[0]?.title ||
                               'UNKNOWN';

            sessionStorage.setItem('shopify_discount', discountCode);

            dataLayer.push({
              event: 'coupon_applied',
              coupon_code: discountCode,
              discount_amount: data.cart.total_discount / 100
            });
          }
          return response;
        });
      });
    }

    return originalFetch.apply(this, args);
  };
})();

// Include discount in checkout events
document.addEventListener('DOMContentLoaded', function() {
  const discountCode = sessionStorage.getItem('shopify_discount') || '';

  // This would be included in your existing checkout tracking
  window.trackBeginCheckout = function(cart) {
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
      event: 'begin_checkout',
      ecommerce: {
        currency: '{{ cart.currency.iso_code }}',
        value: cart.total_price / 100,
        coupon: discountCode,
        items: cart.items.map(item => ({
          item_id: item.sku || item.id,
          item_name: item.title,
          price: item.price / 100,
          quantity: item.quantity,
          coupon: discountCode
        }))
      }
    });
  };
});
</script>

WooCommerce

// Add to functions.php or custom plugin

// Track coupon application
add_action('woocommerce_applied_coupon', function($coupon_code) {
    ?>
    <script>
    sessionStorage.setItem('woo_coupon', '<?php echo esc_js($coupon_code); ?>');

    dataLayer.push({
        event: 'coupon_applied',
        coupon_code: '<?php echo esc_js($coupon_code); ?>',
        discount_amount: <?php
            $coupon = new WC_Coupon($coupon_code);
            echo $coupon->get_amount();
        ?>
    });
    </script>
    <?php
});

// Track coupon removal
add_action('woocommerce_removed_coupon', function($coupon_code) {
    ?>
    <script>
    sessionStorage.removeItem('woo_coupon');

    dataLayer.push({
        event: 'coupon_removed',
        coupon_code: '<?php echo esc_js($coupon_code); ?>'
    });
    </script>
    <?php
});

// Include coupon in purchase event
add_action('woocommerce_thankyou', function($order_id) {
    $order = wc_get_order($order_id);
    $coupons = $order->get_coupon_codes();
    $coupon_code = !empty($coupons) ? implode(',', $coupons) : '';
    ?>
    <script>
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
        event: 'purchase',
        ecommerce: {
            transaction_id: '<?php echo $order->get_order_number(); ?>',
            value: <?php echo $order->get_total(); ?>,
            tax: <?php echo $order->get_total_tax(); ?>,
            shipping: <?php echo $order->get_shipping_total(); ?>,
            currency: '<?php echo $order->get_currency(); ?>',
            coupon: '<?php echo esc_js($coupon_code); ?>',
            items: [
                <?php
                foreach ($order->get_items() as $item) {
                    $product = $item->get_product();
                    ?>
                    {
                        item_id: '<?php echo $product->get_sku() ?: $product->get_id(); ?>',
                        item_name: '<?php echo esc_js($item->get_name()); ?>',
                        price: <?php echo $item->get_total() / $item->get_quantity(); ?>,
                        quantity: <?php echo $item->get_quantity(); ?>,
                        coupon: '<?php echo esc_js($coupon_code); ?>'
                    },
                    <?php
                }
                ?>
            ]
        }
    });
    </script>
    <?php
});

// Track promotional banners (in template file)
?>
<div class="promo-banner"
     data-promotion
     data-promo-id="banner_sale_2025"
     data-promo-name="Spring Sale 2025"
     data-creative-name="Spring Sale Banner"
     data-slot="homepage_top"
     data-location="homepage">
    <a href="/sale">Spring Sale - 30% Off!</a>
</div>

<script>
// Banner impression tracking
document.querySelectorAll('[data-promotion]').forEach(banner => {
    const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting && !entry.target.dataset.tracked) {
                entry.target.dataset.tracked = 'true';

                dataLayer.push({ ecommerce: null });
                dataLayer.push({
                    event: 'view_promotion',
                    ecommerce: {
                        items: [{
                            promotion_id: entry.target.dataset.promoId,
                            promotion_name: entry.target.dataset.promoName,
                            creative_name: entry.target.dataset.creativeName,
                            creative_slot: entry.target.dataset.slot,
                            location_id: entry.target.dataset.location
                        }]
                    }
                });
            }
        });
    }, { threshold: 0.5 });

    observer.observe(banner);
});
</script>

BigCommerce

// Add to theme/assets/js/theme/global.js
import utils from '@bigcommerce/stencil-utils';

class PromotionTracking {
    constructor() {
        this.trackedPromotions = new Set();
        this.init();
    }

    init() {
        this.trackPromotionBanners();
        this.trackCouponApplication();
    }

    trackPromotionBanners() {
        const banners = document.querySelectorAll('[data-promotion-banner]');

        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const banner = entry.target;
                    const promoId = banner.dataset.promoId;

                    if (!this.trackedPromotions.has(promoId)) {
                        this.trackedPromotions.add(promoId);

                        dataLayer.push({ ecommerce: null });
                        dataLayer.push({
                            event: 'view_promotion',
                            ecommerce: {
                                items: [{
                                    promotion_id: promoId,
                                    promotion_name: banner.dataset.promoName,
                                    creative_name: banner.dataset.creativeName,
                                    creative_slot: banner.dataset.slot
                                }]
                            }
                        });
                    }
                }
            });
        }, { threshold: 0.5 });

        banners.forEach(banner => observer.observe(banner));
    }

    trackCouponApplication() {
        // Monitor cart for coupon application
        const cartObserver = setInterval(() => {
            utils.api.cart.getCart({}, (err, response) => {
                if (response && response.coupons && response.coupons.length > 0) {
                    const couponCode = response.coupons[0].code;
                    const lastCoupon = sessionStorage.getItem('bc_last_coupon');

                    if (couponCode !== lastCoupon) {
                        sessionStorage.setItem('bc_last_coupon', couponCode);

                        dataLayer.push({
                            event: 'coupon_applied',
                            coupon_code: couponCode,
                            discount_amount: response.coupons[0].discountedAmount
                        });
                    }
                }
            });
        }, 2000);
    }

    getCurrentCoupon(callback) {
        utils.api.cart.getCart({}, (err, response) => {
            const coupon = response?.coupons?.[0]?.code || '';
            callback(coupon);
        });
    }
}

export default new PromotionTracking();

Magento

<!-- Add to Magento_Checkout/templates/cart/coupon.phtml -->
<script>
require(['jquery', 'Magento_Customer/js/customer-data'], function($, customerData) {
    var cart = customerData.get('cart');

    // Track when coupon is applied
    $(document).on('ajax:addToCart', function(e, data) {
        if (data.coupon_code) {
            sessionStorage.setItem('magento_coupon', data.coupon_code);

            dataLayer.push({
                event: 'coupon_applied',
                coupon_code: data.coupon_code,
                discount_amount: data.discount_amount
            });
        }
    });

    // Monitor cart for coupon changes
    cart.subscribe(function(updatedCart) {
        if (updatedCart.coupon_code) {
            sessionStorage.setItem('magento_coupon', updatedCart.coupon_code);
        }
    });
});
</script>

<!-- Add to Magento_Checkout/templates/success.phtml -->
<?php
$order = $block->getOrder();
$couponCode = $order->getCouponCode();
?>
<script>
require(['jquery'], function($) {
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
        event: 'purchase',
        ecommerce: {
            transaction_id: '<?php echo $order->getIncrementId(); ?>',
            value: <?php echo $order->getGrandTotal(); ?>,
            tax: <?php echo $order->getTaxAmount(); ?>,
            shipping: <?php echo $order->getShippingAmount(); ?>,
            currency: '<?php echo $order->getOrderCurrencyCode(); ?>',
            coupon: '<?php echo $couponCode ? esc_js($couponCode) : ''; ?>',
            items: [
                <?php foreach ($order->getAllVisibleItems() as $item): ?>
                {
                    item_id: '<?php echo $item->getSku(); ?>',
                    item_name: '<?php echo esc_js($item->getName()); ?>',
                    price: <?php echo $item->getPrice(); ?>,
                    quantity: <?php echo $item->getQtyOrdered(); ?>,
                    coupon: '<?php echo $couponCode ? esc_js($couponCode) : ''; ?>'
                },
                <?php endforeach; ?>
            ]
        }
    });
});
</script>

Testing & Validation

Promotion Tracking Checklist

  • Banner Impressions: view_promotion fires when banner visible
  • Banner Clicks: select_promotion fires on click
  • Coupon Application: Custom event fires when coupon applied
  • Coupon in Cart: add_to_cart includes coupon parameter
  • Coupon in Checkout: begin_checkout includes coupon
  • Coupon in Purchase: purchase includes coupon
  • Multiple Coupons: All coupons tracked (concatenated)
  • Auto Discounts: Automatic discounts tracked with special codes
  • Required Fields: All promotions have id, name, creative_name, creative_slot

GA4 Reports to Check

  1. Monetization โ†’ E-commerce purchases โ†’ Coupon

    • Verify coupon codes appear
    • Check revenue by coupon
  2. Advertising โ†’ All campaigns โ†’ Promotions

    • Verify promotion names appear
    • Check clicks and conversions
  3. Engagement โ†’ Events โ†’ view_promotion

    • Verify promotion impressions tracked
    • Check promotion_name parameter

Console Test Script

// Apply a test coupon and track the flow
async function testCouponTracking(testCoupon = 'TEST25') {
  console.log('๐Ÿงช Testing coupon tracking...\n');

  // Simulate coupon application
  sessionStorage.setItem('applied_coupon', testCoupon);

  // Test add to cart
  console.log('1. Testing add_to_cart with coupon...');
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'add_to_cart',
    ecommerce: {
      coupon: testCoupon,
      items: [{ item_id: 'TEST', item_name: 'Test Product', price: 10 }]
    }
  });

  // Check if it was tracked
  const cartEvent = dataLayer.filter(e => e.event === 'add_to_cart').pop();
  if (cartEvent?.ecommerce?.coupon === testCoupon) {
    console.log('โœ“ Coupon tracked in add_to_cart');
  } else {
    console.error('โœ— Coupon NOT tracked in add_to_cart');
  }

  // Cleanup
  sessionStorage.removeItem('applied_coupon');
}

testCouponTracking();

Further Reading

// SYS.FOOTER