Tracking & Attribution

Why Doesn't My Shopify Custom Pixel Fire on Shop Pay Orders?

Your Shopify custom pixel sees card orders but not Shop Pay? Why checkout_completed goes missing, how to confirm it, and how to send those purchases.

Quick answer

Because `checkout_completed` is a browser event, and a Shop Pay checkout does not always finish on a page where your custom pixel gets to run to completion. Accelerated entry points skip earlier checkout steps your code may depend on, and the buyer can leave before the confirmation page renders. The order still exists in Shopify. The reliable fix is to send Purchase from the paid order record on the server, not from the browser.

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Your Shopify custom pixel misses Shop Pay orders because checkout_completed is a browser event, and a Shop Pay checkout does not always end on a page where your pixel runs to completion. The order is real and paid; only the browser signal is missing. The durable fix is to send Purchase from the order record on the server.

The symptom is specific: card checkouts show up in Events Manager, while Shop Pay checkouts produce a paid order in the admin and nothing in Meta.

So the problem is not your pixel id, ad account or dataset. One path through checkout reaches your code and one does not, or reaches it in a state your code does not expect.

Why does checkout_completed fire for card checkouts but not Shop Pay?

A custom pixel only knows about a purchase if the buyer’s browser loads the confirmation step, the pixel loads inside it, and your handler runs without error. Shop Pay adds more ways for any of those three to fail: buyers enter from express buttons that skip earlier steps, confirm on their phone, and leave faster.

Here are the failure modes worth checking, most common first.

Your code depends on an earlier event. Many custom pixels store something during checkout_started or payment_info_submitted — a value, an email, a flag — and read it back in the checkout_completed handler. A Shop Pay express button on a product page or cart can take the buyer through a shorter path. If the earlier event never ran in that session, the variable is empty, the handler throws, and nothing is sent. This is a code bug, not a Shopify bug, and it is the first thing to rule out.

Your code assumes a data shape. Handlers that read deep into the event payload — the first transaction, a gateway name, a shipping line — can break when a wallet payment fills those fields differently. One unguarded property access inside the handler is enough to kill the whole send.

The confirmation page never renders in that browser. Shop Pay can ask the buyer to verify with a code sent to their phone. Buyers switch apps, confirm, and sometimes close the tab before the confirmation page finishes loading. The order is created on Shopify’s servers regardless. The browser event depends on a page the buyer may never see.

The event moved. Post-purchase upsell apps change where the purchase event can fire. If the upsell appears for some payment methods and not others, your pixel may behave differently by method.

Consent blocked the pixel. Custom pixels follow the store’s customer privacy settings, so a buyer who declined tracking is invisible to them. That hits every payment method, but can look like a Shop Pay gap if Shop Pay buyers skew toward one region.

Two paths from a paid Shop Pay order: the browser path through the confirmation page and custom pixel broken at the page load, and the server path from the Shopify order webhook through to Meta's Conversions API completing

How do you confirm Shop Pay is the gap?

Compare paid orders by payment method against the Purchase events Meta received for the same days. If card orders line up with Meta and Shop Pay orders do not, you have found the gap. Then reproduce it once with a real Shop Pay order while watching your pixel’s output, so you know which failure mode it is.

Work through it in this order:

  1. Pick three or four days. Align both tools to the same time zone first, or the edges of your window will lie.
  2. Split your orders by payment method. Export paid orders from the Shopify admin with the payment gateway column, then count card orders and Shop Pay orders separately.
  3. Match them against Meta. In Events Manager, list the Purchase events for those days and their order ids. If your pixel sends the order id in custom data, look each one up. Card ids present and Shop Pay ids missing is the confirmation.
  4. Run one real Shop Pay order. Use a cheap product and a discount code. Test through the express button on a product page, not only through the regular checkout, because that is the path most likely to skip steps.
  5. Watch the handler. Wrap your checkout_completed handler in a try/catch that logs the error and the payload keys. Use Meta’s Test Events tab with a test code so you can see whether a Purchase arrives without polluting live data.
  6. Check where the buyer landed. Did the confirmation page load in the same tab, and did an upsell offer appear first?

A handler that threw is a code fix. A handler that never ran is a delivery problem no browser code fully solves.

Can you fix it inside the custom pixel?

Partly. You can make the handler stop depending on earlier events, guard every optional field, and send the order id as event_id. That fixes the code bugs. It cannot fix buyers who never load the confirmation page, because no browser code runs on a page that never opens.

The defensive version of a custom pixel handler looks like this:

// Subscribe at the top level, not inside another event's callback.
analytics.subscribe('checkout_completed', (event) => {
  try {
    const checkout = event.data?.checkout;
    const orderId = checkout?.order?.id;
    if (!orderId) return;           // nothing to deduplicate on — skip

    const value = checkout?.totalPrice?.amount;
    const currency = checkout?.currencyCode;

    // Send Purchase with the order id as the event id,
    // so a server-side Purchase for the same order deduplicates.
    sendPurchase({ eventId: String(orderId), value, currency });
  } catch (err) {
    console.warn('checkout_completed handler failed', err);
  }
});

Treat field names in that snippet as a pattern, not a reference: confirm each against Shopify’s Web Pixels documentation for your store’s API version before shipping it. sendPurchase stands in for however your pixel forwards the event.

Even a perfect handler leaves a hole: some Shop Pay buyers never give the browser the chance.

Why is the order webhook the reliable place to send Purchase?

Because Shopify creates the order on its own servers whatever payment method the buyer used, and it notifies subscribed apps with an order webhook. A Purchase sent from that webhook exists for every paid order, Shop Pay or card, whether or not any confirmation page loaded. The browser stops being a single point of failure.

The server-side send goes through Meta’s Conversions API. Two things make it work well:

Matching. Meta ties a server event to a person through the identifiers you send: hashed email and phone from the order, the _fbp and _fbc browser cookie values, IP address and user agent. Shop Pay orders carry the buyer’s email, and usually a phone number, so the high-weight identifiers are present. The cookie values must be captured earlier in the visit and joined to the order — the hard part. Why attribution breaks between your landing page and Shopify checkout covers that join.

Deduplication. If you keep the browser Purchase for card orders and add a server Purchase for every order, Meta sees two events for most sales. It deduplicates them when both carry the same event name and the same event_id. Build that id from the order, not a random value; what event id to use for Meta deduplication lays out the rules. The simpler option is to let one source own Purchase and turn the other off. Firing Shopify purchase events server-side without an app walks through building it yourself.

What does a missing Shop Pay purchase actually cost?

Two things: reporting and optimisation. Your ad reports understate revenue by every Shop Pay order that went missing, so return on ad spend looks worse than it is. And Meta’s delivery system learns from the purchases it receives, so it never learns from the buyers who chose the fastest checkout.

Reporting you can reconcile against your admin. Optimisation is worse because it is invisible. Buyers who pay with a saved wallet in a few taps are often the ones who did not hesitate. Those are buyers you want Meta to find more of, and they are the ones missing from its training data.

How does PartialLeads send Shop Pay orders to Meta?

PartialLeads sends Purchase from Shopify’s order webhooks, not from the confirmation page, so a Shop Pay order is sent the same way as a card order: once it is paid. Whether the browser fired checkout_completed does not matter. The order is then matched back to the ad session that started it.

Every paid order is sent. PartialLeads listens for Shopify’s order webhooks and sends a server-side Purchase when the order’s financial_status is paid. Unpaid, pending and cancelled orders are not sent. The value is the full order total, including tax and shipping, after discounts, in the order’s currency. An order is recorded once; later edits to it do not re-send the event.

The order is matched to the visit. The PartialLeads pixel records the visitor from the first page view. When an order arrives, PartialLeads matches it to a session in tiers: the visitor id echoed back with the order first, then email, then phone, then IP. The identity resolution behind it stitches a person’s sessions together, so a buyer who clicked a Meta ad on Monday and paid with Shop Pay on Wednesday can still be tied to the click. Where the ad click carried an fbclid but the _fbc cookie is missing, PartialLeads rebuilds _fbc from it.

Nothing is double-sent by PartialLeads. Each Purchase carries a deterministic event_id built from the pixel id, the event name and the record id, and retries or redelivered webhooks cannot produce a second send.

Purchases ledger listing six paid Shopify orders with order number, source badge, matched or unmatched status, order value and a Meta sent chip on each row

Where you see it working. The Purchases ledger lists every paid order PartialLeads recorded, matched or unmatched to a session. The CAPI activity log shows each Purchase sent to Meta with its delivery status. In the Leads list, the API column shows which conversion APIs each buyer was sent to, and the journey ribbon shows the visits that led to the sale.

The honest constraints. Delivery is the half PartialLeads controls: every paid order is sent. Matching is maximised, not guaranteed; an order with no captured visitor id and an email Meta cannot resolve reaches Meta without a click to tie it to. PartialLeads’ event ids are its own, so it does not deduplicate against your custom pixel or Shopify’s Facebook & Instagram app. If your custom pixel keeps sending Purchase for card orders, switch that off and let one source own Purchase. Keep the Meta base pixel on the store, because server events rely on the _fbp cookie it sets. The wider cleanup lives in how to fix Shopify tracking, attribution and catalog.

What breaks The mechanism Where you see it in the dashboard
Shop Pay buyer never loads the confirmation page Purchase sent from Shopify’s paid-order webhook, not the browser CAPI activity log, one Purchase per paid order
Express checkout skips steps your handler relies on No browser Purchase to break; the order record is the trigger Purchases ledger, Shop Pay order numbers present
Server Purchase has no ad click attached Session match by visitor id, then email, phone and IP; _fbc rebuilt from fbclid Purchases ledger matched/unmatched, journey ribbon
Retries or redelivered webhooks double-count Deterministic event_id per record; each order recorded once CAPI activity log, Purchases ledger
Custom pixel and server both send Purchase Not deduplicated across tools — keep one source Events Manager, browser vs server counts

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Sources


Frequently asked questions

QWhy does my Shopify custom pixel not fire checkout_completed for Shop Pay?
Because the event depends on the buyer's browser loading the confirmation step and your handler running cleanly. Shop Pay buyers often enter through express buttons that skip earlier checkout steps, confirm on their phone, or leave before the page loads. If your handler relies on data from an earlier event, or the page never renders, nothing is sent even though the order exists.
QAre Shop Pay orders missing from Shopify or only from Meta?
Only from Meta. The order is created on Shopify's servers whatever the payment method, so it appears in your admin with its payment and fulfilment. What is missing is the browser event your custom pixel uses to send Purchase. Compare paid orders by payment gateway against Meta's Purchase events to confirm.
QCan I fix Shop Pay tracking with a better custom pixel?
Partly. Subscribing to checkout_completed at the top level, guarding optional fields and not depending on earlier events fixes the code bugs. It cannot fix buyers who never load the confirmation page, because browser code cannot run on a page that never opens. Sending Purchase from the paid-order webhook closes that gap.
QWill I double-count purchases if I send them from the browser and the server?
Meta deduplicates a browser and a server event when both carry the same event name and the same event_id. Build that id from the order rather than a random value. If your tools use different ids, Meta counts both, so the simplest safe setup is one source for Purchase.
QDoes PartialLeads deduplicate against my existing custom pixel?
No. PartialLeads never sends the same Purchase twice itself, but its event ids are its own, so it does not deduplicate against your custom pixel or Shopify's Facebook & Instagram app. If you use PartialLeads for Purchase, turn off Purchase in the other tool and keep the Meta base pixel for the _fbp cookie.
QWill every Shop Pay order be attributed to an ad after switching to server-side?
Every paid order will be sent, but not every one will be tied to an ad click. That depends on whether the visit was captured and joined to the order, and on whether Meta can match the buyer's email, phone and cookies to a person. Delivery is complete; matching is maximised, not guaranteed.

Find the qualified leads your forms are currently throwing away.

Install PartialLeads on one landing page, send traffic, and compare what your CRM captured against what PartialLeads recovered and qualified.