Skip to content
TT
All writing

· 2 min read

Debugging a headless Shopify checkout you don't own

When a headless checkout hand-off fails, the failing surface belongs to Shopify. Here's how to tell your bug apart from platform behaviour.

ShopifyHeadlessDebugging

The hardest bug I've worked on in headless Shopify wasn't in my code. It was at the boundary — the moment a cart built in a Next.js application is handed to Shopify's checkout.

When that hand-off fails, you have a problem that most debugging instincts are wrong about: the surface showing the error is not the surface you control.

The failure mode

A customer adds items to the cart. They click checkout. They land on Shopify's checkout with an empty cart, a partial cart, or an error page. Your logs show nothing, because from your application's perspective nothing failed — you built a cart, you redirected, done.

The evidence you need lives in three places, and none of them is your server log:

  1. The cart state at hand-off. What exactly did you send?
  2. The network boundary. What did Shopify receive and respond with?
  3. The pixel event stream. What did Shopify think happened?

Start with what Shopify observed

A Custom Pixel subscribes to Shopify's own event stream from inside the checkout sandbox. That's the closest you can get to seeing checkout through Shopify's eyes.

analytics.subscribe("checkout_started", (event) => {
  // Forward the checkout's view of the cart to your own logging.
  // When this disagrees with what you *sent*, you've found the seam.
  log("checkout_started", {
    lineItemCount: event.data.checkout.lineItems.length,
    total: event.data.checkout.totalPrice.amount,
  });
});

If checkout_started never fires, the hand-off failed before Shopify got involved — that's yours. If it fires with a line item count that disagrees with the cart you built, the divergence is in what you sent or how it was interpreted. That single distinction cuts the search space roughly in half.

The usual culprits

In my experience the recurring causes are, in order of frequency:

  • Stale cart identifiers. A cart the application still considers valid has expired or been completed on Shopify's side.
  • Variant availability drift. The variant was purchasable when it entered the cart and isn't at hand-off. This is much more common on large catalogues with external inventory sync.
  • Region mismatch. A cart built against one region's context handed to another region's checkout.

None of these produce an obvious error. They produce a checkout that quietly disagrees with your cart.

What actually fixed it

Instrumentation, not a code change. Once the cart state at hand-off was logged alongside the pixel's view of checkout_started, the failures stopped being mysterious and became a diff — two objects that should match and didn't.

That's the real lesson for headless work generally. When you hand off to a system you don't own, your job isn't to prevent every failure. It's to make sure that when one happens, you can prove whose it is.

Hiring for work like this?

I'm Thang Viet To — a senior backend & platform engineer with 9+ years on event pipelines, multi-tenant commerce systems and the platforms behind them.

Get in touch