Shopify CAPI Setup

Install SignalSight Conversions API on Shopify — via a Customer Events custom pixel with analytics.subscribe() event handlers, or theme.liquid Liquid snippets.

In This Page

This guide covers two ways to send SignalSight events from a Shopify store. You need a SignalSight account and tracker ready before you begin — see Account Creation and Meta (Facebook) CAPI Setup if you haven't set those up yet.

Choose Your Installation Method

  • Customer Events pixel — a single custom pixel in Shopify's Customer Events settings. Uses analytics.subscribe() handlers, covers checkout events on all plans, and needs no theme file changes.
  • Theme.liquid installation — paste Liquid snippets into individual theme files. Works on all plans but requires per-template edits and Shopify Plus for checkout Purchase events.

Which should I use?

Prefer Customer Events for new setups — especially if you need Purchase tracking without Shopify Plus. Use theme.liquid only if your store already relies on Liquid-based tracking or Customer Events is unavailable in your region.

Customer Events Pixel Installation

Shopify's Customer Events lets you create a custom pixel that fires on storefront and checkout events — without editing theme files. All events are handled through analytics.subscribe() callbacks in a single code block.

Note

This is the recommended approach. Do not run it alongside the Theme.liquid installation section below — duplicate events will be sent.

Create Your Custom Pixel

  1. In Shopify admin, go to SettingsCustomer events.
  2. Click Add custom pixel and give it a name (e.g. SignalSight).
  3. Paste the base code and event subscribers below into the pixel code editor.
  4. Replace your-alias-code in the loader URL with your tracker key from the SignalSight dashboard.
  5. Set the pixel permission to Not required or match your consent settings, then click Save and Connect.

Base Code and Loader

Place this at the top of your custom pixel. It loads the Meta pixel guard, initializes the SignalSight queue, and injects the loader script.

Customer Events pixel — base code
!function (f, b, e, v, n, t, s) {
  if (f.fbq) return; n = f.fbq = function () {
    if (arguments[1] === "Purchase" && typeof arguments[3]?.eventID === "undefined" && arguments[2]?.content_type !== "product") return;
    if (arguments[1] === "AddToCart" && typeof arguments[3]?.eventID === "undefined" && arguments[2]?.content_type !== "product") return;
    if (arguments[1] === "PageView" && typeof arguments[3]?.eventID === "undefined") return;
    if (arguments[1] === "Search" && typeof arguments[3]?.eventID === "undefined") return;
    if (arguments[1] === "ViewContent" && typeof arguments[3]?.eventID === "undefined" && arguments[2]?.content_type !== "product") return;
    n.callMethod ?
    n.callMethod.apply(n, arguments) : n.queue.push(arguments)
  };
  if (!f._fbq) f._fbq = n; n.push = n; n.loaded = !0; n.version = '2.0';
  n.queue = []; t = b.createElement(e); t.async = !0;
  t.src = v; s = b.head.getElementsByTagName(e)[0];
  s.parentNode.insertBefore(t, s)
}(window, document, 'script',
  'https://connect.facebook.net/en_US/fbevents.js');

!function(f, b, e, v, t, s) {f.p2sq = f.p2sq || [];if (f.p2sf) return;f.p2sf=true;
t = b.createElement(e);t.async = !0;t.src = v;s = b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t, s);
}(window,document,'script','https://cpi.ssevt.com/js/v4.2/your-alias-code');

window.p2sq = window.p2sq || [];
p2sq.push({et:'Init'});

Event Subscribers

Add these analytics.subscribe() handlers below the base code. They cover every standard event — including Purchase via checkout_completed, which works on all Shopify plans without checkout.liquid access.

The checkout_completed handler sends user details from billing/shipping address on purchase. Adjust the phone normalization logic (90 country prefix) to match your store's market.

Customer Events pixel — event subscribers
analytics.subscribe("page_viewed", (event) => {
  window.p2sq = window.p2sq || [];
  const new_url = event.context.document.location.href;
  window.history.replaceState({}, '', new_url);
  p2sq.push({et: 'PageView'});
});

analytics.subscribe('checkout_completed', (event) => {
  window.p2sq = window.p2sq || [];

  const checkoutData = event.data.checkout;
  const billingAddress = checkoutData.billingAddress;
  const shippingAddress = checkoutData.shippingAddress;

  let phone = billingAddress?.phone ?? '';
  phone = phone.replace(/[^0-9]+/g, "");
  if (phone.startsWith("0")) {
    phone = phone.substring(1);
  }
  if (!phone.startsWith("90")) {
    phone = "90" + phone;
  }

  let userDetails = {
    fn: (billingAddress?.firstName || shippingAddress?.firstName || '').toLowerCase(),
    ln: (billingAddress?.lastName || shippingAddress?.lastName || '').toLowerCase(),
    em: (checkoutData.email ?? '').toLowerCase(),
    ph: phone,
    ct: (billingAddress?.city || shippingAddress?.city || '').toLowerCase(),
    zp: (billingAddress?.zip || shippingAddress?.zip || '').replace(/\s/g, ""),
    country: (billingAddress?.country || shippingAddress?.country || '').toLowerCase()
  };

  const new_url = event.context.document.location.href;
  window.history.replaceState({}, '', new_url);

  p2sq.push({et:'Init', p: userDetails});

  p2sq.push({et:'Purchase', p:{
    value: event.data.checkout.totalPrice.amount,
    currency: event.data.checkout.currencyCode,
    contents: event.data.checkout.lineItems.map(item => ({
      id: item.variant.id,
      quantity: item.quantity
    })),
    content_type: 'product'
  }});
});

analytics.subscribe("product_viewed", (event) => {
  window.p2sq = window.p2sq || [];
  const new_url = event.context.document.location.href;
  window.history.replaceState({}, '', new_url);
  p2sq.push({et: 'ViewContent', p: {
    contents: [{id: event.data.productVariant.id, quantity: 1}],
    content_name: event.data.productVariant.title,
    currency: event.data.productVariant.price.currencyCode,
    value: event.data.productVariant.price.amount
  }});
});

analytics.subscribe("search_submitted", (event) => {
  window.p2sq = window.p2sq || [];
  const new_url = event.context.document.location.href;
  window.history.replaceState({}, '', new_url);
  p2sq.push({et: 'Search', p: {
    search_string: event.data.searchResult.query
  }});
});

analytics.subscribe("product_added_to_cart", (event) => {
  window.p2sq = window.p2sq || [];
  let _cart_params;
  if (typeof (event.data.cartLine.merchandise.productVariant) != 'undefined') {
    _cart_params = {
      contents: [{id: event.data.cartLine.merchandise?.productVariant?.id, quantity: 1}],
      content_name: event.data.cartLine.merchandise?.productVariant?.title,
      currency: event.data.cartLine.merchandise?.productVariant?.price?.currencyCode,
      value: event.data.cartLine.merchandise?.productVariant?.price?.amount
    };
  } else {
    _cart_params = {
      contents: [{id: event.data.cartLine.merchandise.id, quantity: 1}],
      content_name: event.data.cartLine.merchandise?.title,
      currency: event.data.cartLine.merchandise?.price?.currencyCode,
      value: event.data.cartLine.merchandise?.price?.amount
    };
  }
  const new_url = event.context.document.location.href;
  window.history.replaceState({}, '', new_url);
  p2sq.push({et: 'AddToCart', p: _cart_params});
});

analytics.subscribe("payment_info_submitted", (event) => {
  window.p2sq = window.p2sq || [];
  const new_url = event.context.document.location.href;
  window.history.replaceState({}, '', new_url);
  p2sq.push({et: 'AddPaymentInfo'});
});

analytics.subscribe("checkout_started", (event) => {
  window.p2sq = window.p2sq || [];
  const new_url = event.context.document.location.href;
  window.history.replaceState({}, '', new_url);
  p2sq.push({et:'InitiateCheckout', p:{
    value: event.data.checkout.totalPrice.amount,
    currency: event.data.checkout.currencyCode,
    content_type: 'product',
    contents: event.data.checkout.lineItems.map(item => ({
      id: item.variant.id,
      quantity: item.quantity
    }))
  }});
});

Tip

Customer Events also fires AddPaymentInfo via payment_info_submitted — an event not covered in the theme.liquid approach. For PII hashing requirements on user fields, see PII Hashing.

Theme.liquid Installation

Add event snippets directly to your theme files. Each standard Meta event maps to a specific template — PageView, ViewContent, AddToCart, Search, InitiateCheckout, and Purchase.

Open the Theme Code Editor

  1. Log in to your Shopify admin account.
  2. Under Online StoreThemes, click the three dots next to your theme and select Edit code.

Note

Theme file names vary by design — product.liquid vs main-product.liquid, for example. Verify events in your browser's network tab after publishing.

PageView and Base Code

Add the Meta pixel guard and SignalSight loader to theme.liquid. The Init block passes SHA-256-hashed customer fields when a user is logged in. Replace your-alias-code with the key from your tracker detail page.

theme.liquid — Meta pixel guard
<script>
!function (f, b, e, v, n, t, s) {
  if (f.fbq) return; n = f.fbq = function () {
    if (arguments[1] === "Purchase" && typeof arguments[3]?.eventID === "undefined" && arguments[2]?.content_type !== "product") return;
    if (arguments[1] === "AddToCart" && typeof arguments[3]?.eventID === "undefined" && arguments[2]?.content_type !== "product") return;
    if (arguments[1] === "PageView" && typeof arguments[3]?.eventID === "undefined") return;
    if (arguments[1] === "Search" && typeof arguments[3]?.eventID === "undefined") return;
    if (arguments[1] === "ViewContent" && typeof arguments[3]?.eventID === "undefined" && arguments[2]?.content_type !== "product") return;
    n.callMethod ?
    n.callMethod.apply(n, arguments) : n.queue.push(arguments)
  };
  if (!f._fbq) f._fbq = n; n.push = n; n.loaded = !0; n.version = '2.0';
  n.queue = []; t = b.createElement(e); t.async = !0;
  t.src = v; s = b.head.getElementsByTagName(e)[0];
  s.parentNode.insertBefore(t, s)
}(window, document, 'script',
  'https://connect.facebook.net/en_US/fbevents.js');
</script>
theme.liquid — SignalSight Init, PageView, and loader
<script>
window.p2sq = window.p2sq || [];
p2sq.push({et:'Init',p:{
  em: {% assign email_hash = customer.email | sha256 %} "{{ email_hash }}",
  ph: {% assign phone_hash = customer.phone | sha256 %} "{{ phone_hash }}",
  fn: {% assign firstname_hash = customer.first_name | sha256 %} "{{ firstname_hash }}",
  ln: {% assign lastname_hash = customer.last_name | sha256 %} "{{ lastname_hash }}"
}});

p2sq.push({et: 'PageView'});

!function(f, b, e, v, t, s) {f.p2sq = f.p2sq || [];if (f.p2sf) return;f.p2sf=true;
t = b.createElement(e);t.async = !0;t.src = v;s = b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t, s);
}(window,document,'script','https://cpi.ssevt.com/js/v4.2/your-alias-code');
</script>

ViewContent

Add to product.liquid or main-product.liquid. Adjust currency to match your store.

main-product.liquid — ViewContent
<script>
p2sq.push({et: 'ViewContent', p: {
  content_ids: ['{{ product.id }}'],
  content_type: 'product',
  value: {{ product.price | divided_by: 100.00 }},
  currency: 'USD'
}});
</script>

Search

Add to main-search.liquid. Uses the {{ search.terms }} Liquid variable for the query string.

main-search.liquid — Search
<script>
p2sq.push({et: 'Search', p: {
  search_string: '{{ search.terms }}',
  currency: 'USD',
}});
</script>

AddToCart

Add to product.liquid or main-product.liquid. Listens for a click on name="add" — verify this selector matches your theme's button.

main-product.liquid — AddToCart
<script>
document.querySelector('[name="add"]').addEventListener('click', function() {
  p2sq.push({
    et: 'AddToCart', p: {
    content_ids: ['{{ product.id }}'],
    content_type: 'product',
    value: {{ product.price | divided_by: 100.00 }},
    currency: 'USD',
  }});
});
</script>

InitiateCheckout

Add to cart.liquid or main-cart.liquid. Builds content_ids from all cart line items.

main-cart.liquid — InitiateCheckout
<script>
p2sq.push({
  et: 'InitiateCheckout', p: {
  content_ids: [{% for item in cart.items %}'{{ item.product.id }}'{% unless forloop.last %}, {% endunless %}{% endfor %}],
  content_type: 'product',
  value: {{ cart.total_price | divided_by: 100.00 }},
  currency: 'USD',
}});
</script>

Purchase

Purchase events in checkout.liquid or thank_you.liquid require Shopify Plus. The first_time_accessed check prevents duplicate fires on page reload.

Note

Need Purchase tracking without Shopify Plus? Use the Customer Events pixel approach above instead — it captures checkout_completed on all plans.

checkout.liquid / thank_you.liquid — Purchase (Shopify Plus)
{% if first_time_accessed %}
<script>
p2sq.push({
  et: 'Purchase',
  p: {
    content_ids: [{% for line_item in checkout.line_items %}'{{ line_item.product.id }}'{% unless forloop.last %}, {% endunless %}{% endfor %}],
    content_type: 'product',
    value: '{{ checkout.total_price | money_without_currency }}',
    currency: 'USD'
  },
  eid: '{{ checkout.order_id }}'
});
</script>
{% endif %}

Alternatively, use the post-purchase page snippet if your checkout flow supports it:

Post-purchase page — Purchase
<script>
(function() {
  window.p2sq = window.p2sq || [];

  if (!Shopify.wasPostPurchasePageSeen) {
    var order = window.Shopify.order;

    p2sq.push({
      et: 'Purchase',
      p: {
        content_ids: order.lineItems.map(function (item) {
          return {
            id: Number(item.id).toString(),
            quantity: item.quantity
          };
        }),
        content_type: 'product',
        value: order.totalPrice,
        currency: 'SEK'
      },
      eid: Number(order.id).toString()
    });
  }

  Shopify.on('CheckoutAmended', function(newOrder, previousOrder) {
    var oldItems = previousOrder.lineItems.map(function (line) { return line.id; });

    var addedItems = newOrder.lineItems.filter(
      function (line) { return oldItems.indexOf(line.id) < 0; }
    );

    if (addedItems.length === 0) {
      return;
    }

    p2sq.push({
      et: 'Purchase',
      p: {
        content_ids: addedItems.map(function(item) {
          return {
            id: Number(item.id).toString(),
            quantity: item.quantity
          };
        }),
        content_type: 'product',
        value: order.totalPrice,
        currency: 'SEK'
      },
      eid: Number(order.id).toString()
    });
  });
})();
</script>

Only activate tracking after the user has given consent through your Consent Management Platform (CMP). SignalSight respects your consent configuration — events are not sent until consent is granted. Configure pixel permissions in Shopify Customer Events to align with your CMP.

Frequently Asked Questions

Should I use theme.liquid or Customer Events?

Customer Events is the recommended approach for new setups — one code block, checkout events on all plans, no theme edits. Use theme.liquid only if you have an existing Liquid-based setup or cannot access Customer Events.

Can I use both installation methods at the same time?

No. Running both will send duplicate events. Pick one method and remove the other before going live.

Where do I find my-alias-code?

Open your tracker in the SignalSight dashboard. The installation snippet on the tracker detail page contains your unique key.

Why is there a modified fbq loader?

The guard prevents duplicate Meta Pixel events from firing without an eventID. SignalSight handles server-side delivery — this filter stops conflicting browser-side fbq calls.

How does Customer Events capture Purchase without Shopify Plus?

The checkout_completed analytics event fires on the order confirmation page for all plans. The subscriber sends a Purchase p2sq.push with line items, value, and user details from the checkout object.