Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 4 min read

How I Engineered a Shopify Tracking Widget to Reduce WISMO

When I first started helping a friend manage their Shopify store, I thought the hard part was getting sales. I was wrong. The hard part was what happened after the sale. Within a week of launching, my friend’s inbox was

When I first started helping a friend manage their Shopify store, I thought the hard part was getting sales. I was wrong. The hard part was what happened after the sale.

Within a week of launching, my friend’s inbox was completely flooded. "Where is my order?" "Did you ship my stuff?" "Why hasn't my tracking updated?" These WISMO (Where Is My Order) tickets were eating up hours of our day. The default Shopify order status page was functional, but it felt disconnected from the rest of the brand, and customers were still emailing us because they didn't know how to navigate to it.

I looked at existing solutions, but many required heavy theme modifications that broke during updates, or they locked essential features behind high-tier pricing. So I decided to build something.

The Problem with Post-Purchase

WISMO tickets are the silent killer of e-commerce margins. They aren't just a nuisance; they represent a direct cost in support time. When a customer can't easily find their tracking information, their default action is to email your support team.

Beyond the operational cost, there's a brand experience cost. You spend hours optimizing your checkout flow and branding, only to hand the customer off to a generic, unbranded tracking page the moment they pay. The post-purchase experience is often treated as an afterthought, but it's actually the longest part of the customer journey. If the tracking experience is clunky, it erodes the trust you just built.

The Technical Approach

To solve this, I needed to build a tracking widget that was deeply integrated into the store's theme, easy to install, and secure. I decided to use Shopify’s App Embed blocks for the frontend, combined with a Node.js backend listening to webhooks for the data.

The biggest architectural decision was how to inject the widget. Modifying theme files directly is a nightmare because theme updates overwrite your changes. Shopify’s App Embed blocks solve this by injecting snippets via the theme editor, surviving theme upgrades.

Here is the Liquid and JavaScript snippet we use for the App Embed block. It safely passes the order data to the frontend and initializes the widget asynchronously so it doesn't block the main thread or hurt page load speeds.

{% 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://cdn.parcelglance.com/widget.js';
  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>

By using script.async = true, we ensure the tracking widget loads in the background. The onload callback guarantees we don't try to initialize the widget before the library is ready.

On the backend, we need to know when an order is actually fulfilled. Instead of polling the Shopify API (which burns through rate limits), we use webhooks. Security is paramount here, so every request must be verified using the HMAC signature provided by Shopify.

Here is the Express.js webhook handler we use to process fulfillment updates:

const crypto = require('crypto');

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

// Handle fulfillment update webhook
app.post('/webhooks/orders/fulfilled',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const verified = verifyShopifyWebhook(
      req.body,
      req.headers['x-shopify-hmac-sha256'],
      process.env.SHOPIFY_WEBHOOK_SECRET
    );

    if (!verified) return res.status(401).send('Invalid');

    const order = JSON.parse(req.body.toString('utf8'));
    // Store fulfillment data for tracking lookup
    await db.fulfillments.upsert({
      orderId: order.id,
      trackingNumber: order.fulfillments[0]?.tracking_number,
      carrier: order.fulfillments[0]?.tracking_company,
      status: 'shipped',
    });

    res.status(200).send('OK');
  }
);

Notice that we use express.raw() to capture the raw body. This is strictly required because the HMAC signature is calculated against the raw JSON string, not the parsed JavaScript object. If you parse it first, the hash won't match, and your webhook will fail.

Practical Results and Learnings

Building this out taught me a lot about the quirks of the Shopify ecosystem. What worked really well was the App Embed approach. Merchants could install the app and toggle the tracking widget on or off directly from their theme editor without touching a single line of code.

One thing that surprised me was how much the visual design mattered. Even though the tracking data was accurate, customers perceived the tracking as more reliable when it was wrapped in a clean, branded UI that matched their store's typography and colors. The post-purchase anxiety is largely psychological.

If I were to change one thing from the initial build, it would be how I handled carrier API rate limits. Early on, I tried fetching tracking updates directly from the frontend widget. It was a security risk and a performance bottleneck. Moving to a backend webhook architecture and caching the carrier status locally made the widget significantly faster and more resilient to carrier API downtime.

Wrapping Up

I originally built this to solve my friend's WISMO problem, but it turned out to be a solid solution for other store owners as well. I've packaged it into a free tool called Parcelglance. It adds a branded, real-time tracking widget to your store and helps keep those support tickets at bay.

You can check out the free Shipment Tracking Widget at parcelglance.com.

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

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.