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
- In Shopify admin, go to Settings → Customer events.
- Click Add custom pixel and give it a name (e.g. SignalSight).
- Paste the base code and event subscribers below into the pixel code editor.
- Replace
your-alias-codein the loader URL with your tracker key from the SignalSight dashboard. - 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.
!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.
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
- Log in to your Shopify admin account.
- Under Online Store → Themes, 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.
<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><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.
<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.
<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.
<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.
<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.
{% 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:
<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>Consent Management & Data Privacy
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.