DEV Community

seller-mind
seller-mind

Posted on

Beyond the Checkout: Building a Custom Shopify Tracking Experience

When I first started building custom solutions for a friend's Shopify store, I assumed the hardest part would be driving traffic and getting sales. I was wrong. The real nightmare started after the checkout.

Every day, the support inbox was flooded with the exact same question: "Where is my order?" (WISMO). Customers were anxious, and I was spending hours manually checking carrier websites just to reply to emails. It was exhausting and completely unscalable. I realized the post-purchase experience was broken, and the lack of visibility was creating unnecessary friction. So, I decided to build something to fix it.

The Hidden Cost of the Post-Purchase Void

The post-purchase phase is often an afterthought in e-commerce. We spend countless hours optimizing the checkout flow, but once the customer clicks "Buy," we usually just send them a generic confirmation email and hope for the best.

But shipping takes time, and anxiety builds. When customers can't easily see their package's status, they email support. For example, for a small store processing a few dozen orders a day, this might mean 20 extra support emails. For a larger store, it's thousands.

The cost isn't just the time spent answering tickets; it's customer trust. If a buyer has to fight to find out where their stuff is, they aren't coming back. I needed a way to give them a branded, real-time tracking experience directly on the store, without requiring them to leave or dig through their inbox.

Architecting the Solution

Building a tracking widget for Shopify comes with specific constraints. You want it to be easy to install, highly customizable, and lightweight so it doesn't hurt store performance.

I decided to build this as a Shopify App Embed. This was a crucial architectural decision. Instead of forcing merchants to edit their theme code manually (which is a huge friction point and prone to breaking during theme updates), App Embeds allow merchants to toggle the widget on or off directly from the Shopify theme editor.

Here is the core Liquid and JavaScript snippet that renders the widget on the order status page:

{% comment %}
  Renders shipment tracking widget on order status pages
{% endcomment %}

<div id="parcelglance-widget" 
     data-order-id="{{ order.id }}"
     data-order-number="{{ order.name }}"
     data-store-name="{{ shop.name }}"
     style="min-height: 200px;">
</div>

<script>
(function() {
  const script = document.createElement('script');
  script.src = 'https://parcelglance.com/widget.js?shop=mystore';
  script.async = true;
  script.onload = function() {
    Parcelglance.init({
      orderId: document.getElementById('parcelglance-widget').dataset.orderId,
      orderNumber: document.getElementById('parcelglance-widget').dataset.orderNumber,
      storeName: document.getElementById('parcelglance-widget').dataset.storeName,
      container: 'parcelglance-widget',
    });
  };
  document.head.appendChild(script);
})();
</script>
Enter fullscreen mode Exit fullscreen mode

By passing the Liquid variables (order.id, order.name, shop.name) directly into data attributes, the frontend JavaScript can initialize the widget without needing an extra API call just to figure out which order to display.

Loading the script asynchronously (script.async = true) ensures that the tracking widget never blocks the main thread, keeping the page load time fast.

On the backend, the architecture relies heavily on webhooks to keep data fresh. Instead of polling carrier APIs every time a customer loads the tracking page, the backend listens to Shopify's fulfillment webhooks. We use HMAC verification to ensure the requests are genuinely from Shopify:

const crypto = require('crypto');

function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
  const hash = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('base64');
  return hash === hmacHeader;
}
Enter fullscreen mode Exit fullscreen mode

Once verified, the fulfillment data is stored locally, meaning the frontend widget can fetch tracking updates instantly from our database rather than waiting on slow third-party carrier APIs.

Practical Results and Lessons Learned

The biggest surprise during development was how much the visual design mattered. Initially, I just wanted a functional table showing "Shipped" and "Delivered." But I quickly realized that matching the store's theme was critical. If the widget looked like a generic third-party plugin, it broke the immersion of the store. I spent a significant amount of time building out the customization options so the widget inherits the store's typography and colors, creating a seamless post-purchase engagement experience.

What worked really well was the App Embed approach. Merchants loved that they didn't have to touch a single line of Liquid code to get it working. It drastically reduced the support overhead just from the installation process alone.

If I were to start over, I'd probably spend more time on the initial webhook architecture from day one. Handling edge cases with multi-carrier shipments and partial fulfillments took a few iterations to get right, and retrofitting the database schema later was a bit painful.

Wrapping Up

If you're dealing with the same post-purchase chaos I was, I've packaged this solution into a free tool you can use today. The Parcelglance Shipment Tracking Widget is free to install and helps you set up a branded tracking page to keep your customers in the loop and reduce those WISMO support tickets. You can check it out and add it to your store at parcelglance.com.


Full disclosure: I'm the developer of Parcelglance. I built this because I needed it myself.

Top comments (0)