Tracking & Attribution

Why Did My Ad Conversions Drop to Zero After Shopify's Thank-You Page Upgrade?

Orders still arriving but Meta and Google show zero purchases after Shopify's thank-you page upgrade? Here's the exact cause and how to fix it.

Quick answer

Because your purchase event was firing from a script on the old thank-you page, and Shopify's checkout extensibility migration took that script away. Orders keep landing in the admin because orders never depended on the script. The conversion did. When the page stopped running your custom JavaScript, the Purchase event stopped being sent, so Meta and Google show zero purchases against traffic that is still converting. The fix is to stop firing purchases from a page the shopper may never load, and send them from the order record instead.

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 ad conversions dropped to zero after Shopify’s thank-you page upgrade because the purchase event was fired by a script on that page, and the upgrade removed the place that script lived. Orders never depended on it, which is why they kept arriving. The conversion did, so it stopped. Nothing is wrong with your pixel ID, your ad account, or your campaigns.

This is one of the cleanest diagnoses in ecommerce tracking, because the symptom pair is so specific: order count normal, platform-reported purchases zero, starting on a date. Very few failures look like that.

What actually changed on Shopify’s thank-you page?

Shopify moved checkout, the thank-you page and the order-status page onto its checkout extensibility platform. In the old model, merchants could paste arbitrary JavaScript into an Additional Scripts box that ran on the order-status page after a successful order. That box is where a huge share of the world’s Shopify conversion tracking lived: Meta’s Purchase call, Google Ads conversion snippets, affiliate postbacks, hand-rolled fetch() calls to a warehouse.

On the new pages, arbitrary theme-level JavaScript no longer runs there. Third-party tracking is expected to go through Shopify’s Customer Events pixels instead, which run in a sandboxed environment and receive a defined event stream rather than raw page access. Shopify publishes the retirement dates on its own changelog and developer changelog — check those rather than trusting a date you read in a forum thread, because the deadlines moved more than once and differed for checkout versus the thank-you and order-status pages.

The practical effect for you is simple. Any tracking that was typed into that box is gone. It did not error. It was not disabled in your ad account. The page that used to execute it no longer executes it.

Why did orders keep coming while conversions went to zero?

Because an order is created by the checkout on Shopify’s servers, and a conversion event is created by a browser. Those are two independent systems, and only one of them ran your code.

The shopper’s payment is authorised, Shopify writes the order, your admin count goes up, your fulfilment queue fills, your revenue report is correct. None of that touches JavaScript. Meanwhile the event that tells Meta “this click became a sale” was a line of script on a page — and if that line does not execute, the platform has no way to know the sale happened.

Read that gap the right way and it is actually good news. You have not lost orders or money. You have lost reporting and optimisation signal, which is fixable retroactively for reporting and forward-only for optimisation. Your campaigns spent the last N days bidding without purchase feedback, which is the real cost.

How do you confirm it’s the thank-you page and not the ad account?

Four checks, in this order. Each one takes minutes and rules out a whole class of cause.

  1. Draw the line on a calendar. Export daily order counts from your admin and daily purchases from Ads Manager for 60 days. If orders are flat and platform purchases fall off a cliff on a single day, you are looking at a deployment, not a market shift. Note the date.
  2. Match the date to your store’s migration. Compare that date to when your store moved to checkout extensibility, or to the platform-wide retirement date for the surface you were using. A match is close to conclusive.
  3. Check whether the event fires at all. Place a real test order — a cheap product, a 100%-off discount code — and watch Meta’s Events Manager test-events view and your browser’s network tab on the thank-you page. If nothing fires, the script is gone. If it fires but is not attributed, you have a different, identifier-shaped problem instead.
  4. Look at what else was in that box. Additional Scripts usually held more than one tag. If your Google Ads conversions, affiliate tracking and a Klaviyo or warehouse call all went quiet on the same day, that confirms the box, not any single platform. Migrating them one at a time is part of the wider Shopify tracking, attribution and catalog fix.

If orders also dropped, stop here — that is a merchandising or checkout problem and none of this applies.

Where should the purchase event actually fire?

On the order, not on a page. This is the design principle worth taking away from the whole incident.

A thank-you page is the least reliable moment in the entire funnel to fire your most valuable event. The shopper may close the tab the instant payment succeeds. They may pay in an external app and never return. Their browser may block the request. The page may be replaced by your platform, which is exactly what happened here. Every one of those failure modes is invisible to you and silently costs a conversion.

The order record has none of those properties. It exists on the server, once, with a total, a currency and a customer. Firing from the order means the event’s existence depends on the sale, not on the shopper’s browser cooperating.

Diagram of a Shopify order webhook path: paid order in the Shopify admin flows to a match tier then fans out to Meta, Pinterest, TikTok and a Google Sheet, bypassing the thank-you page entirely

What are your options for fixing it?

Four, with honest limits.

Shopify’s own channel apps. The Facebook & Instagram app and Google & YouTube app send their own purchase events and survive the migration because Shopify maintains them. Fastest fix, and the right first move for many stores. Limits: you get the platform’s own event and its own attribution, with little control over payload or de-duplication against anything else you run.

A custom Customer Events pixel. Add a web pixel in Shopify admin that listens for checkout_completed and calls the platform from the sandbox. This is the direct replacement for the old script and it is the supported path. Limits: it is still a browser event, so blockers, closed tabs and certain payment flows will cost you some of them. The sandbox also does not give you a durable visitor identifier, which matters for attribution more than for counting.

Server-side through a container. Route events via a server-side tag container. Real control, real payload hygiene. Limits: cost and maintenance, and if your trigger is still a browser event you have moved the request but not removed the dependency.

The order webhook. Have Shopify POST each order to something that sends the conversion server-side through the Conversions API. This is the option the failure argues for, because it is the only one whose trigger is the order itself. There is a walkthrough of this path without adding an app if you want to build it yourself.

Whichever you pick: run one source per event. If the channel app and your custom pixel and a plugin all fire Purchase, you will replace zero conversions with triple-counted ones. Platforms only de-duplicate events that share an event ID and event name, and independent tools do not share IDs — which is the whole subject of why Meta stops de-duplicating.

Can you backfill the conversions you missed?

Partially, and less than you want. Meta clamps incoming event_time values to a seven-day window, so events older than that either fail or land with a shifted timestamp — see the 7-day event time window for what that does to your reporting. Google Ads offline imports allow a longer click-age window but enforce their own rules on conversion time versus click time.

Practically: backfill the last week where the platform will accept it, accept the gap before that, and annotate the date in whatever document your team uses to explain performance. Do not re-upload three months of orders and expect your historical charts to heal.

How does PartialLeads keep this from happening again?

By never making the purchase event depend on a page. The Shopify purchase in PartialLeads comes from the store’s order webhooks, server-side. A thank-you page change, a closed tab, a blocked request, a payment flow that returns the shopper somewhere unexpected — none of them sit in the path.

The mechanism, end to end. Shopify POSTs the order. Every paid order produces a conversion event: financial_status = paid is the gate, so pending, failed and cancelled orders are not sent, and a cash-on-delivery or bank-transfer order fires when you mark it paid. The recorded value is the full order total including tax and shipping, after discounts. The order is matched to a browsing session by a tiered join — the visitor ID echoed back from the storefront first, then email, then phone, then IP — and then fanned out to Meta, Pinterest, TikTok and the Google Ads sheet. Each order is recorded once; a later edit to the same order does not re-send it.

Alongside that, the Customer Events pixel supplies the upstream funnel — product views, add-to-cart, checkout started, payment info added — so the ad platform’s algorithm gets the whole sequence rather than a bare Purchase. Those two paths carry separate responsibilities on purpose: the pixel does funnel signal, the webhook owns money.

Two honest constraints. First, PartialLeads uses its own deterministic event IDs, so it cannot de-duplicate against another app’s or plugin’s browser pixel — if you keep Shopify’s Facebook & Instagram app firing Purchase, turn one of them off. Second, the split that matters: dispatch is controlled, matching is not. Every paid order in your admin produces an event, and you can reconcile admin order count against dispatched events to prove it. Whether the platform then ties that event to a person and a click depends on the identifiers present and on the platform’s own graph. Any vendor promising the second half is selling you something they do not own.

Meta CAPI activity log showing purchase event delivery resuming after a gap, with a 14-day health sparkline, delivery rate and per-event match tiers

Where you watch it work: the Purchases ledger shows every order that arrived with its match tier and a matched-versus-unmatched split, so an event that did not resolve to a session is visible rather than absent. The Meta CAPI page shows the activity log and a 14-day delivery strip — that strip is where a migration gap and its recovery are legible at a glance. The Attribution report shows what the recovered purchases did to your channel table, which is the number your scale decisions actually run on.

What breaks The mechanism Where you see it in the dashboard
Thank-you page stops running your script, so Purchase never fires Purchases come from Shopify order webhooks, server-side — no page in the path Purchases ledger: every paid order present with its match tier
Orders arrive but the platform never learns the sale happened Every paid order fans out to Meta, Pinterest, TikTok and the Google Ads sheet Meta CAPI page: activity log plus a 14-day delivery strip
Sale recorded but credited to Direct Tiered match to the browsing session — visitor ID echo, then email, phone, IP Attribution report, and the journey ribbon on the Leads list
Two tools both firing Purchase after the fix Deterministic event IDs mean PartialLeads never double-sends its own events Meta CAPI activity log: one row per order, per event

The lesson generalises past Shopify. Any event that only exists because a specific page loaded a specific script is one platform release away from zero. Move your revenue events to the record, and page changes become cosmetic.

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

https://developers.facebook.com/docs/marketing-api/conversions-api

https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events

https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/server-event


Frequently asked questions

QMy Shopify orders are normal but Meta shows zero purchases. What is the single most likely cause?
A purchase event that was firing from a script on the old thank-you or order-status page, which Shopify's checkout extensibility migration removed. Orders are created server-side and never needed that script, so they kept arriving. The conversion event did need it, so it stopped. Check whether your platform purchases fall off on a single date while order counts stay flat.
QDid Shopify break my ad tracking on purpose?
No. The old model let any merchant run arbitrary JavaScript on the page that displays a completed order, including the customer's details. The new Customer Events pixels trade raw page access for a sandbox and a defined event stream, which is a reasonable security posture. The cost is that every tag typed into the old box needed migrating, and many stores did not know theirs existed.
QWill a custom web pixel firing checkout_completed fully replace what I lost?
It replaces the mechanism, not the reliability. It is still a browser event, so closed tabs, ad blockers and some payment flows will cost you a share of them, and the sandbox does not hand you a durable visitor identifier. Use it for funnel events, and prefer the order record as the trigger for the purchase itself.
QCan I recover the conversions I missed during the gap?
Only the recent tail. Meta clamps event timestamps to a seven-day window, so older events either fail or land with a shifted time. Google Ads offline imports allow a longer click-age window but enforce their own conversion-time rules. Backfill what the platform accepts, then annotate the gap in your reporting instead of trying to repair the history.
QHow do I avoid double-counting once I fix it?
Run one source per event. Platforms only de-duplicate events sharing an event ID and event name, and independent tools do not share IDs with each other. If Shopify's channel app, a custom pixel and a plugin all send Purchase, you get three. Pick one owner per event and switch the others off for that event.
QIs this the same problem as Shopify labelling my sales as Direct?
No, and it is worth telling them apart. This is a missing event: the platform never hears about the sale. Direct attribution is a missing identifier: the platform hears about the sale but cannot tie it to a click. A store can have both at once, and fixing the event does not fix the join.
QDo unpaid or cancelled orders send a conversion?
In PartialLeads, no. The gate is Shopify's `financial_status = paid`, so pending, failed and cancelled orders are not sent. A cash-on-delivery or bank-transfer order fires when you mark it paid, which can be days after the click — worth remembering against Meta's seven-day event-time window.
QIf I move purchases server-side, will my numbers finally match Meta's?
They will get much closer and stop reading zero, but two systems with different attribution models do not print the same number. What you can prove is dispatch: every paid order in your admin produced an event, reconcilable by counting both. Whether the platform resolves each event to a user and a click is its half of the job, not yours.

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.