Web Development

Checkout Extensibility: Your Orders Still Land, Your Attribution Is Already Dead

Two Shopify deprecations landed this summer and neither one produces an error a customer can see. Checkout keeps taking money; the logic bolted onto checkout stops running. We stopped treating order volume as a health check, and started reconciling counts instead.

KAKabir AnandLead Developer
8 min read
Editorial photograph of a retail counter where a receipt printer is still printing an order while the analytics screen on the wall behind it sits blank

The client emailed us about their ad account, not their store. Meta had lost roughly half of its attributed purchases since the first week of July, revenue in Shopify was flat week over week, and the working theory in their Slack was that the ad platform had changed something. It had not. The store had been dropping post-purchase tracking for six weeks, and every order in that window had completed perfectly normally.

Nobody was negligent. There was no alert to miss, no failed deploy, no red dashboard. The one signal everyone in that company watched — orders arriving — kept saying the same thing it always said.

That failure shape is going to keep repeating for the rest of this year, because Shopify has spent 2026 removing the places merchants used to put custom logic. Scripts stopped executing on 30 June. Legacy Thank you and Order status customisations went out of support for non-Plus stores on 26 August, which was two days ago. Neither removal raises anything a customer or an on-call engineer can see. Checkout keeps taking money. The things attached to checkout quietly stop happening.

Two deprecations, and one of them already fired

These get discussed as one migration because they share a season, but they are separate systems with separate blast radii, and conflating them is how teams end up half-done. It is worth being precise about what moved where.

  • Server-side commercial logic moved to Functions.Tiered discounts, custom shipping rates, payment method ordering — anything that used to be a Ruby script deciding what the customer is charged. When a Script stops running, nobody gets an error; a customer simply pays the undiscounted price. That arrives at support as “my promo code didn’t work,” one ticket at a time, and gets closed one ticket at a time.
  • Post-purchase page customisations moved to checkout extensions.The Additional scripts box and hand-edited Thank you and Order status content are the classic dumping ground — referral widgets, review requests, survey embeds, subscription upsells. Most of it accreted over years and none of it is in anyone’s architecture diagram.
  • Analytics moved into the Web Pixels sandbox.This is the one that catches good teams. Pixels now run in a sandboxed worker against a standard customer-event API — no document, no DOM, no reading the order object off the page. A tag written to pull values off the window does not throw when you move it. It reads undefined and fires an event with nothing in it.

The asymmetry matters for planning. Nothing about the second and third items is a syntax port; the execution model changed underneath them. A Function is a compiled, sandboxed, input-output unit that runs inside Shopify's own pipeline rather than a script with ambient access to the cart. A pixel is an event consumer, not a page participant. Teams that scoped this as "find the old code, paste it into the new place" are the teams still finding orphaned tags in September.

Flat vector illustration of two parallel pipelines running left to right, the upper one carrying payment tokens intact, the lower one severed mid-run with its contents spilling away
One of these pipes is monitored. It is not the one that broke.

We stopped trusting the order feed

The reason all of this hides is architectural, and it is not a bug. Shopify separated the payment path from the extension surface on purpose: an extension that fails must not be able to take checkout down with it. That is unambiguously the right design decision, and it has a direct consequence for the people operating the store. The blast radius of a broken extension is precisely the set of things you most want to measure, and precisely the set of things that will never page you.

A checkout that is up proves the payment path is up. It is not evidence about anything else, and we stopped writing it into status reports as though it were.

Kabir Anand, Lead Developer

So the check has to be reconciliation rather than uptime. Count the thing that definitely happened, count the downstream record of it, and compare them over the same window. Orders are the only source of truth on a Shopify store; every analytics system, every CRM, every warehouse table is a claim about orders, and a claim can be checked. This is a dozen lines and it runs on a schedule.

jobs/reconcile-purchase-coverage.ts
1// Orders are the source of truth. Everything downstream is a claim about them.
2const orders = await shopify.orders.count({ created_at_min: from, created_at_max: to });
3const tracked = await warehouse.countEvents("purchase", from, to);
4 
5const coverage = tracked / orders;
6 
7// Never 1.00 — blockers and consent take a stable slice. Alert on drift from
8// your own baseline, never on an absolute, or the alarm gets muted in a week.
9if (coverage < BASELINE * 0.9) {
10  await pager.warn("purchase coverage " + coverage.toFixed(2) + " vs " + BASELINE);
11}
12 
13// Same shape for the money path: discounted line items as a share of orders.
14// A Function that stopped running shows up here as a clean step down.

Run daily, that would have surfaced the six-week outage inside about thirty-six hours. The detail that makes it work in practice is the baseline: coverage never reaches 1.00, because ad blockers and consent refusals take a stable slice off the top, and every store's slice is a different size. Teams that alert on an absolute number get a false positive on day one, mute the alert, and are back where they started. Alert on the ratio moving relative to last month and it stays quiet until something is genuinely wrong.

The same trick covers the commercial plane. Discounted orders as a share of total orders is a boring, extremely stable number for any store with a live promotion, and a Function that quietly stopped applying shows up as a step change in it within a day. We now put both ratios on the same graph before we touch anything else in a checkout migration, because they are also the only honest way to prove afterwards that the rebuild worked.

Why we stopped calling this a migration

Our first estimate on a Scripts-to-Functions job was built the way you would build any port: count the scripts, price each one, add a margin. Four scripts, a week and a half. It took nearly three, and the overrun had almost nothing to do with Rust, Wasm, or the Functions API, all of which are fine and well documented.

It went on archaeology. The discount Script was the only written record of what the business had actually promised customers over four years — a wholesale tier, a bundle rule that only applied below a certain cart weight, a legacy grandfathered rate for about two hundred accounts that predated the current pricing page entirely. None of that was in a spec, a ticket or anyone's head. Half the job was reading Ruby to a founder in a call and asking, one branch at a time, whether the company still meant it.

That is why we stopped quoting these as migrations and started quoting them as audits with a rebuild attached. The framing changes what the client expects to be asked, and it changes who needs to be available — which is the part that actually determines whether it takes ten days or twenty.

What the audit is actually looking for

  • Every tag in the Additional scripts box, named and owned — with a decision to port or delete, because roughly a third of them are usually dead already.
  • Which commercial rules exist only in Script source and nowhere in writing, and who is authorised to confirm they are still policy.
  • Any pixel that reads the page rather than consuming events, since those degrade to silence rather than failing in the sandbox.
  • Post-purchase apps installed years ago against the old surface, which stop rendering without uninstalling themselves.
  • The store's real purchase-coverage baseline, captured before the rebuild, so afterwards there is something to compare against.
  • Test orders that actually exercise a discount, a custom shipping rate and a post-purchase upsell — a plain full-price order proves only the payment path.
Isometric illustration of a checkout process shown as stacked platform layers, with the upper customisation layers detaching and drifting away while the payment layer beneath stays solid
The base layer never moved. Everything a merchant built on top of it did.

If you only check one thing this week

Pull purchase events and Shopify orders for July and August, and the same two numbers for May. If the ratio has moved by more than a few points, you have already lost data, and the priority is stopping the loss rather than explaining it. Then open the Additional scripts box and read what is in it, out loud, to someone who works in marketing. On every store we have audited this year there has been at least one tag nobody could account for.

It is also worth putting the next date in the calendar while the context is loaded. Shopify's card-deposit endpoint requires an mTLS client certificate from 15 October, and the certificate has a one-year lifetime. The install is a morning's work; the thing that will actually bite is the renewal twelve months later, when whoever did the install has moved on. The deliverable there is expiry monitoring and a rehearsed rotation with a named owner, not a certificate in a vault.

The general lesson has outlived this particular platform for us. When a vendor removes an extension point, the failure never lands on the vendor's critical path — they made sure of that — so it lands on yours, without an exception, without a log line, and without anyone noticing until a number in a different system looks strange. The question to ask before any deprecation deadline is not what will break. It is: if this silently stopped working today, which graph would move, and is anybody looking at it?

Keep Reading

More from the blog

The Engineering Partner You Can Build On

Reliable software takes an experienced team that owns delivery end to end. Here’s the track record behind ours.

11+

Years Building Custom Software

320+

Projects Delivered Across Web, Mobile & AI

85%

Repeat Client Rate

12+

Countries Served Worldwide

Trusted by startups and enterprises worldwide

Work With Us

Let’s create
with purpose

Share your goals, timeline, and challenges — we’ll respond with clarity and next steps.

Ambitious ideas deserve thoughtful execution. Start the conversation and let’s define what success looks like.

Team

Acetrum

Est. 2015

4.9/5

Trusted by
top brands

Services interested in:

By submitting, I confirm I’ve read and agree with Privacy and Cookie Policies.

Newsletter

Signals worth
paying attention

No recycled headlines — just the patterns we’re seeing across real client work, distilled into one read a month.

A curated digest of practical thinking and real-world brand perspectives monthly.

No spam. Unsubscribe anytime.