Skip to content
  • There are no suggestions because the search field is empty.

Developing a Custom Web Pixel for the Convert Shopify App

Market-Specific Goal Tracking for Shopify Checkout Events with Convert Attribution

Owner: George F. Crewe

IN THIS ARTICLE YOU WILL

Overview

This guide explains how to create a Shopify Custom Web Pixel that enables market-specific goal tracking while maintaining full experiment and variation attribution when using the Convert Shopify Experience App.

Custom Web Pixels are necessary when you need:

  • Separate goals for different markets (e.g., "Begin Checkout Canada" vs. "USA")
  • Custom conversion events not covered by the app's built-in tracking
  • Advanced attribution logic that combines multiple data sources

Understanding the Technical Challenge

The Problem

The Convert Shopify App operates as an "App Embed" within Shopify's ecosystem, which creates a secure sandbox environment for Shopify Web Pixels. This sandbox cannot directly access:

  • Standard browser cookies set on the main page
  • The convert JavaScript object from the main Convert tracking script
  • Variables from other scripts or domains

The Solution

The Convert Shopify App bridges this gap by storing all essential experiment metadata in Shopify Cart Attributes, specifically a base64-encoded attribute called __data. Your Custom Web Pixel can read this data and send conversions to Convert.com with full attribution.

Where Experiment Metadata Is Stored

When a visitor participates in a Convert experiment, the Shopify App stores their experiment context in cart attributes that persist through checkout.

The __data Attribute (Primary Source)

Location: event.data.checkout.attributes[__data]

This is a base64-encoded JSON object containing:

{
  "visitorId": "1738012108903-0.269766...",
  "bucketing": [
    { 
      "experienceId": "100341351", 
      "variationId": "1003177123", 
      "goals": [] 
    }
  ],
  "segments": {
    "country": "CA",
    "browser": "CH",
    "devices": ["DESK"],
    "source": "direct",
    "visitorType": "new"
  },
  "accountId": "123456",
  "projectId": "789012",
  "currency": "CAD"
}

Key Fields:

Field

Description

Use Case

visitorId

Unique visitor identifier across sessions

User recognition

bucketing

Array of experiment participation records

Attribution mapping

segments.country

2-letter country code (CA, US, GB, etc.)

Market routing

segments.device

Device type array (DESK, MOB, TAB)

Device segmentation

accountId

Your Convert account ID

API authentication

projectId

Your Convert project ID

API endpoint construction

Supplemental Cart Attributes

Each active experiment is also stored individually as:

  • Attribute Name: experience_{experienceId}
  • Value: {variationId}

For example: experience_100341351 = 1003177123

Step-by-Step: Creating a Market-Specific Custom Web Pixel

Step 1: Create Market-Specific Goals in Convert

Before building the pixel, set up your goals in Convert:

  1. Log into Convert.com dashboard
  2. Navigate to Goals & Events > Goals
  3. Click "Create Goal" for each market:
    • Example: "Begin Checkout - Canada"
    • Example: "Begin Checkout - USA"
    • Example: "Begin Checkout - UK"
  4. Copy the Goal ID for each (found in goal settings)

Goal IDs look like: 1234567890

Create a reference table:

const MARKET_GOALS = {
  'CA': 'YOUR_CANADA_GOAL_ID',      // e.g., '1738912345'
  'US': 'YOUR_USA_GOAL_ID',         // e.g., '1738912346'
  'GB': 'YOUR_UK_GOAL_ID',          // e.g., '1738912347'
  'DE': 'YOUR_GERMANY_GOAL_ID',     // e.g., '1738912348'
  'FR': 'YOUR_FRANCE_GOAL_ID',      // e.g., '1738912349'
};

Step 2: Disable Built-In Goal Tracking

To prevent double-tracking, configure the Shopify App's built-in goal:

  1. Go to Shopify Admin > Apps > Convert Shopify Experience
  2. Open App Settings
  3. Find the Goal Configuration section
  4. Leave the checkout_started field empty (uncheck or remove)
  5. Save settings

Why? The built-in Web Pixel fires automatically for all visitors. If you're using a Custom Pixel for market-specific goals, disable the corresponding built-in goal.

Step 3: Add Custom Web Pixel in Shopify

  1. In Shopify Admin, go to Settings > Customer Events
  2. Scroll to Web Pixels section
  3. Click "Add custom pixel"
  4. Configure the pixel:
    • Name: "Convert Market-Specific Tracking" (or your preferred name)
    • Events: Select checkout_started (and any others you need)
    • Pixel Code: Paste the code below

Full Custom Pixel Code Template

analytics.subscribe('checkout_started', async (event) => {
  const attributes = event.data?.checkout?.attributes || [];

  // Extract the __data cart attribute
  const dataAttr = attributes.find(a => a.key === '__data');
  if (!dataAttr) return;

  try {
    // Decode the base64-encoded JSON
    const shopifyData = JSON.parse(atob(dataAttr.value));

    // Destructure key values
    const { 
      visitorId, 
      bucketing, 
      segments, 
      accountId, 
      projectId 
    } = shopifyData;

    // Determine the market-specific goal ID
    const country = segments?.country; // "CA", "US", "GB", etc.

    // Map countries to goal IDs
    const MARKET_GOALS = {
      'CA': 'YOUR_CANADA_GOAL_ID',
      'US': 'YOUR_USA_GOAL_ID',
      'GB': 'YOUR_UK_GOAL_ID',
      'DE': 'YOUR_GERMANY_GOAL_ID',
      'FR': 'YOUR_FRANCE_GOAL_ID',
      // Add more markets as needed
    };

    const goalId = MARKET_GOALS[country];
    if (!goalId) {
      console.log(`No goal configured for country: ${country}`);
      return; // Skip if no goal mapped for this market
    }

    // Build the experiment-to-variation attribution map
    const bucketingData = Object.fromEntries(
      bucketing.map(b => [b.experienceId, b.variationId])
    );

    // Prepare the tracking payload
    const payload = {
      accountId,
      projectId,
      enrichData: true,
      source: 'shopifya',
      visitors: [{
        visitorId,
        segments,
        events: [{
          eventType: 'conversion',
          data: {
            goalId,
            bucketingData // Full experiment attribution
          }
        }]
      }]
    };

    // Send to Convert API
    await fetch(
      `https://${projectId}.metrics.convertexperiments.com/v1/track/${accountId}/${projectId}`,
      {
        method: 'POST',
        headers: { 
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        },
        body: JSON.stringify(payload)
      }
    );

    console.log(`Market-specific conversion tracked successfully:`, {
      country,
      goalId,
      visitorId
    });

  } catch (error) {
    console.error('Error tracking market-specific conversion:', error);
    // Optionally report to monitoring service
  }
});

Step 4: Test the Pixel

  1. Set up a test flow:
    • Add item to cart
    • Proceed to checkout
    • Simulate a checkout start (you don't need to complete purchase)
  2. Verify in browser console:
    • Open DevTools on checkout page
    • Look for logs: Market-specific conversion tracked successfully
    • Confirm the correct country and goal ID were used
  3. Check Convert dashboard:
    • Go to Reporting > Conversions
    • Verify the conversion appears under your market-specific goal

Check Audiences to see if segment data is populated

Supporting Multiple Events

You can subscribe to various Shopify customer events. Here's how to adapt the pixel for different use cases:

Event Types Available

Event

When It Fires

Typical Use

checkout_started

User begins checkout flow

Begin tracking

payment_info_submitted

Payment information entered

Payment success

checkout_completed

Order confirmation page

Purchase completion

order_created

Order successfully created

Final conversion

Example: Track Both Start and Completion

// Track checkout starts (for funnel analysis)
analytics.subscribe('checkout_started', async (event) => {
  await trackConversion(event, 'CHECKOUT_START_GOAL_ID');
});

// Track completed purchases
analytics.subscribe('checkout_completed', async (event) => {
  await trackConversion(event, 'PURCHASE_GOAL_ID');
});

// Centralized tracking function
async function trackConversion(event, goalId) {
  const attributes = event.data?.checkout?.attributes || [];
  const dataAttr = attributes.find(a => a.key === '__data');
  if (!dataAttr) return;

  const shopifyData = JSON.parse(atob(dataAttr.value));
  const { visitorId, bucketing, segments, accountId, projectId } = shopifyData;

  const bucketingData = Object.fromEntries(
    bucketing.map(b => [b.experienceId, b.variationId])
  );

  await fetch(
    `https://${projectId}.metrics.convertexperiments.com/v1/track/${accountId}/${projectId}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        accountId,
        projectId,
        enrichData: true,
        source: 'shopifya',
        visitors: [{
          visitorId,
          segments,
          events: [{
            eventType: 'conversion',
            data: { goalId, bucketingData }
          }]
        }]
      })
    }
  );
}

Adding Custom Data to Segments

If you need to pass additional context beyond what the Shopify App provides, extend the segments object before sending:

const enrichedSegments = {
  ...segments,
  // Add custom data available at checkout time
  cartTotal: event.data?.checkout?.total_price || null,
  cartItemQuantity: event.data?.checkout?.line_items?.length || null,
  customerEmail: event.data?.checkout?.email || null,
  marketingConsent: event.data?.checkout?.marketing_consent?.opt_in_level || null,
  referralSource: document.referrer || null,
};

Then include enrichedSegments in your tracking payload instead of raw segments.

Handling Edge Cases

Case 1: Missing __data Attribute

Scenario: Visitor arrives via direct URL without going through a Convert landing page.

Solution: Check for attribute existence before processing:

const dataAttr = attributes.find(a => a.key === '__data');
if (!dataAttr) {
  // No experiment data — still track but mark as organic
  console.log('No experiment data found, tracking without attribution');
  // Optional: track with empty bucketingData
}

Case 2: Failed API Request

Scenario: Network error or Convert API unavailable.

Solution: Implement retry logic with exponential backoff:

async function sendToConvert(url, payload, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      });

      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return await response.json();
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
    }
  }
}

Case 3: Multiple Active Experiments

Scenario: Visitor is participating in 3+ simultaneous experiments.

Solution: The bucketing array already handles this — it contains all active experiments. Just ensure you preserve the full array when creating bucketingData:

// Correct: preserves all experiments
const bucketingData = Object.fromEntries(
  bucketing.map(b => [b.experienceId, b.variationId])
);

// INCORRECT: only takes first experiment
const bucketingData = {
  [bucketing[0].experienceId]: bucketing[0].variationId
};

Alternative Approach: Reporting Only (No Custom Pixel)

If you only need market-level breakdowns in reports (not separate goals), you don't need a Custom Web Pixel:

What the App Already Provides

The built-in tracking already sends segments.country with every conversion. You can use Convert's interface to filter and segment:

  1. Go to Reports > Conversions
  2. Add a column for Country (from segments)
  3. Apply a filter for specific countries
  4. Compare metrics across markets

When this works:

  • ✅ You want to compare Canada vs. USA performance in one goal
  • ✅ You're comfortable using Convert's native segmentation
  • ✅ You don't need separate KPI dashboards per market

When you need Custom Pixel:

  • ❌ You need distinct goals in reporting/alerting systems
  • ❌ You require automated notifications per market
  • ❌ You're using external tools that query Convert API by goal ID

Debugging Tips

Check Pixel Execution

// Add debug logging
console.log('Custom Pixel fired!', {
  checkout: event.data?.checkout,
  attributes: event.data?.checkout?.attributes?.map(a => a.key)
});

Inspect Cart Attributes

In browser DevTools console during checkout:

// Shopify provides analytics global
analytics.ready(() => {
  // This won't show checkout data yet (not submitted), but good for debugging setup
  console.log('Analytics ready');
});

Validate API Response

Wrap the fetch call in error handling to see server responses:

try {
  const response = await fetch(endpoint, { method: 'POST', body: JSON.stringify(payload) });
  const result = await response.json();
  console.log('Convert API response:', result);
} catch (error) {
  console.error('API failed:', error);
}

Quick Reference

Need

Solution

Key Code

Market-specific goals

Custom Web Pixel + MARKET_GOALS map

See template above

Country detection

segments.country field in __data

const country = segments?.country;

Experiment attribution

bucketingData map from bucketing array

Object.fromEntries(bucketing.map(...))

Multiple markets

Extend MARKET_GOALS object

'XX': 'YOUR_GOAL_ID'

Different events

Subscribe to payment_info_submitted, checkout_completed

analytics.subscribe('...')

Reporting only

Use built-in tracking + Convert segmentation

No custom code needed

Prevent double-tracking

Disable checkout_started in app config

Uncheck goal in Shopify App settings