Tracking & Attribution

Why Doesn't My Custom Buy Now Button Fire InitiateCheckout on Shopify?

Buy Now skips the cart, so a cart-based InitiateCheckout never fires. Here's why it happens on Shopify and how to fire it from checkout_started.

Quick answer

Because your InitiateCheckout event is almost certainly wired to something the Buy Now button skips: the cart page, a click on the cart's Checkout button, or an old checkout script. A Buy Now button sends the shopper straight into checkout, so a tracker that waits for the cart never runs. The fix is to fire InitiateCheckout from Shopify's own checkout_started customer event, which runs inside checkout itself no matter how the shopper got there, and to keep exactly one source sending it.

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 custom Buy Now button doesn’t fire InitiateCheckout because the event is attached to a step the button skips. Most Shopify setups send InitiateCheckout when the shopper views the cart or clicks the cart’s Checkout button. Buy Now jumps from the product page straight into checkout, so that trigger never runs. AddToCart may still log, and Purchase still arrives, but the middle of your funnel goes blank.

Nothing is wrong with your pixel ID or your ad account. The event is simply listening in the wrong place.

What does a Buy Now button actually do differently?

A Buy Now button skips the cart. It either adds the item and redirects to checkout in one step, or it builds a checkout link directly for that variant. The shopper never sees the cart page and never clicks the cart’s Checkout button, which is exactly where most InitiateCheckout triggers live.

Compare the two paths for the same shopper, Ava, who clicks a Meta ad for a $60 jacket:

  • Cart path: product page → Add to cart → cart page → Checkout button → checkout → order.
  • Buy Now path: product page → Buy Now → checkout → order.

Every event you fire on the cart page, or on a click inside the cart drawer, exists only on the first path. If your theme developer built a custom Buy Now button, it usually does its own add-and-redirect in JavaScript, and nobody added tracking to that handler.

This also explains the odd pattern people report: AddToCart fires (because the custom button does add the item) but InitiateCheckout never does (because the cart is never shown).

Why do so many Shopify setups tie InitiateCheckout to the cart?

Because for years the only place a merchant could run their own code was the storefront theme, and the last storefront moment before checkout is the cart. Checkout itself was locked down. So guides, apps and agencies wired InitiateCheckout to the cart’s Checkout button click, and it worked, as long as every buyer went through the cart.

Shopify’s move to checkout extensibility and customer events changed where tracking is supposed to live, but plenty of themes still carry the old click-based trigger. If your tracking was moved during the thank-you page upgrade, the Purchase event may have been rebuilt properly while InitiateCheckout was left on the cart button.

There are three common ways it goes wrong:

  1. Click trigger on the cart Checkout button. A theme script or a Google Tag Manager (GTM) trigger listens for clicks on the cart’s button. Buy Now never touches it.
  2. Cart page view trigger. InitiateCheckout fires when the cart page loads. Buy Now never loads it.
  3. A custom pixel subscribed to the wrong event. Someone subscribed to cart_viewed or product_added_to_cart and labelled it InitiateCheckout. It fires for cart buyers and for AddToCart, never for the checkout step itself.

How do you confirm the Buy Now button is the cause?

Run one test order through each path and compare what reaches the ad platform. It takes about five minutes and settles the question without touching code.

  1. Open Meta Events Manager and go to Test Events for your dataset (Meta’s name for the pixel plus its server events).
  2. In a private window, add a product to the cart the normal way and go to checkout. Watch for AddToCart and InitiateCheckout.
  3. Open a new private window, go to a product page and click Buy Now. Watch the same panel.
  4. If InitiateCheckout shows on step 2 but not step 3, the event is tied to the cart.

Then check where the event is defined. Look in your theme code for fbq('track', 'InitiateCheckout', in GTM for a trigger on the cart button, and in Settings → Customer events in the Shopify admin for any custom pixel. Whichever one sends InitiateCheckout is the one to move.

One caveat: express wallet buttons (Shop Pay, Apple Pay, Google Pay) on the product page can open their own payment sheet rather than the normal checkout page. Test each wallet you offer separately rather than assuming it behaves like Buy Now.

Where should InitiateCheckout fire instead?

InitiateCheckout should fire from Shopify’s checkout_started customer event, not from anything in the storefront theme. checkout_started is a standard event that Shopify emits inside checkout itself, so it runs when checkout begins whether the shopper came from the cart, a Buy Now button, or a checkout link in an email.

Two routes into Shopify checkout — the cart path and the Buy Now path — both converging on the checkout_started customer event, which sends InitiateCheckout server-side

The principle is the same one that fixes purchase tracking: attach each event to the step it describes, not to a button that usually comes before it. “The shopper started checkout” is only true once they are in checkout.

A minimal custom pixel looks like this. It runs in Shopify’s customer-events sandbox, so the Meta base code has to be loaded inside the same pixel above it:

// Shopify admin → Settings → Customer events → Add custom pixel
// (Meta base code with fbq('init', '<PIXEL_ID>') loaded above this)
analytics.subscribe("checkout_started", (event) => {
  const checkout = event.data.checkout;
  fbq("track", "InitiateCheckout", {
    value: checkout.totalPrice?.amount,
    currency: checkout.currencyCode,
    content_ids: checkout.lineItems.map((li) => li.variant?.id),
    content_type: "product",
    num_items: checkout.lineItems.length,
  }, {
    // Reuse this same id if you also send the event server-side
    eventID: "ic_" + event.id,
  });
});

Then delete the old cart-button trigger. If you leave it in place, cart buyers will send InitiateCheckout twice: once from the click and once from checkout.

What happens if you fix it with both a pixel and a server event?

You get double counting unless both copies share an event ID. Meta de-duplicates a browser event and a server event only when they carry the same event_name and the same event_id. Two InitiateCheckout events from two tools with different IDs are two checkouts as far as Meta knows.

This bites stores that run Shopify’s Facebook & Instagram app, a custom pixel, and a server-side tool at the same time. Each one can send InitiateCheckout with its own IDs. If Events Manager shows InitiateCheckout above AddToCart, suspect this before anything else, and use a Meta CAPI deduplication checklist to find the second sender.

The rule that avoids it: one source per event. Pick the tool that sends InitiateCheckout and switch that event off everywhere else.

Does a missing InitiateCheckout actually hurt your ads?

Yes, in two ways, though not the way people fear. It does not lose purchases. It does distort the funnel data the ad platform learns from and the reports you make decisions with.

First, optimization. If you run campaigns optimized for InitiateCheckout, or you build retargeting audiences from people who started checkout, every Buy Now shopper is invisible to both. Your best-intent shoppers, the ones who skipped the cart because they had already decided, are the ones missing from the audience.

Second, diagnosis. A funnel report that shows 400 AddToCart events and 90 InitiateCheckout events reads like a cart-page problem. If a third of your buyers use Buy Now, a chunk of that drop is a measurement gap, not a UX problem. You could spend a sprint redesigning a cart that was fine. If you care about tying abandoned checkouts to a traffic source, the checkout-start event has to exist for every path in first.

How does PartialLeads fire InitiateCheckout for Buy Now shoppers?

PartialLeads installs on Shopify as a Customer Events pixel, so it listens to Shopify’s own checkout events instead of the theme. It picks up checkout_started inside checkout, whichever button got the shopper there, and sends InitiateCheckout server-side through the Conversions API to Meta, and to Pinterest and TikTok when those are connected.

It is part of a wider ecommerce fan-out: ViewContent, AddToCart, Search, InitiateCheckout and AddPaymentInfo go out from the same pixel path, not just Purchase. So the ad platform sees the whole funnel for Buy Now shoppers, not a funnel with a hole in the middle. For Pinterest specifically, see how checkout events are sent server-side to Pinterest.

PartialLeads CAPI activity log mockup listing InitiateCheckout events delivered to Meta, Pinterest and TikTok with event ids and status dots, beside a 14-day delivery health strip

Three mechanics make it safe to switch on:

  • Deterministic event IDs. Each event’s ID is built from the pixel, the event name and the record, so a retry can never send the same InitiateCheckout twice from PartialLeads.
  • Variant ids in content_ids. Ecommerce events send the variant id (falling back to the product id), the same id the catalog feed uses, so events and feed line up.
  • Purchases don’t depend on the browser. The Purchase itself comes from Shopify’s order webhook, so every paid order produces a conversion even when a browser event is lost.

The honest limits. PartialLeads uses its own event IDs and does not de-duplicate against another app’s browser pixel. If Shopify’s Facebook & Instagram app or a custom pixel already sends InitiateCheckout, switch that one off so there is one source per event. Matching each event to a person still depends on the identifiers captured and on the platform’s own graph, so matching is maximized, never guaranteed. And we don’t send InitiateCheckout to Google Ads: Google receives purchase conversions as rows in a Google Sheet you import on a schedule.

Where you check it: the visitor’s Journey timeline shows the checkout step with no cart view before it, which is what a Buy Now shopper looks like. The Meta CAPI page’s activity log lists each InitiateCheckout with its delivery status, and Meta’s Events Manager shows the same events arriving server-side. For the rest of the Shopify stack, start from the Shopify tracking, attribution and catalog guide.

What breaks The mechanism Where you see it in the dashboard
InitiateCheckout wired to the cart, so Buy Now shoppers never send it Customer Events pixel listens to checkout_started inside checkout, for every path in Journey timeline: checkout step with no cart view before it
Browser event lost to a blocker or closed tab InitiateCheckout forwarded server-side to Meta, Pinterest and TikTok Meta CAPI activity log, Events Manager
Retries or redelivery double-counting checkouts Deterministic event_id with unique dedup per config Meta CAPI activity log (one row per event)
Two tools each sending InitiateCheckout Not solved by any tool: keep one source per event Events Manager event counts vs AddToCart
Funnel reports blaming the cart for a tracking gap Full ecommerce fan-out: ViewContent, AddToCart, InitiateCheckout, AddPaymentInfo, Purchase Meta CAPI page, Purchases ledger

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 AddToCart fire but InitiateCheckout doesn't when customers use Buy Now?
Because the custom Buy Now button adds the item to the cart, which fires AddToCart, and then redirects straight to checkout. If InitiateCheckout is tied to the cart page or the cart's Checkout button, the shopper never reaches that trigger. Fire InitiateCheckout from Shopify's checkout_started customer event instead, which runs inside checkout for every route in.
QDoes Shopify's dynamic checkout button fire checkout_started?
When it sends the shopper into Shopify's checkout, checkout_started runs there like any other checkout. Express wallet buttons such as Shop Pay, Apple Pay and Google Pay can open their own payment sheet instead, so behaviour can differ by wallet and device. Test each wallet you offer in Meta's Test Events panel rather than assuming.
QCan I just add fbq InitiateCheckout to my Buy Now button's click handler?
You can, but it counts clicks, not checkouts. A shopper who double-clicks, or whose redirect fails, still sends the event. It also leaves your cart path on a different trigger, which is how duplicates start. Firing from checkout_started means one trigger, one meaning, for every path.
QWill fixing InitiateCheckout double-count my checkouts?
It will if the old cart trigger stays on, or if two tools send the same event with different event IDs. Meta only merges a browser and server event when both share the event name and event ID. Remove the old trigger and keep one source per event.
QDoes PartialLeads remove duplicate InitiateCheckout events from other apps?
No. PartialLeads never sends the same event twice itself, because its event IDs are deterministic, but it does not de-duplicate against another app's or plugin's browser pixel. If Shopify's Facebook & Instagram app or a custom pixel already sends InitiateCheckout, switch that one off so each event has a single source.
QDoes a missing InitiateCheckout lose me purchases in Meta?
No. Purchase is a separate event, and if it is sent from the order rather than a browser script it arrives regardless. What you lose is funnel data: checkout-based audiences miss Buy Now shoppers, campaigns optimized for InitiateCheckout see fewer signals, and funnel reports overstate cart drop-off.

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.