DEV Community

seller-mind
seller-mind

Posted on

How I Built a Shopify Tracking Widget to Stop WISMO Tickets

When I first started helping a friend manage their Shopify store, I thought the hard part was getting the first sale. I was wrong. The real headache started exactly three days after the first sale. My inbox was flooded with variations of the same question: "Where is my order?"

It’s the dreaded WISMO (Where Is My Order) ticket. I was spending hours copy-pasting tracking numbers from carrier sites into email replies. It was exhausting, unscalable, and honestly, a terrible customer experience. So, I decided to build something to fix it.

The Black Hole of Post-Purchase

The post-purchase experience is often an afterthought in e-commerce. We spend weeks optimizing the checkout flow, but the moment the customer clicks "Pay," they enter a black hole. They get a generic confirmation email, and then... silence. Until they start emailing support.

WISMO tickets aren't just a nuisance; they are a massive drain on resources. For a small store, answering these manually means taking time away from marketing or product development. For larger stores, it means hiring more support staff. Plus, a generic, unbranded tracking page breaks the illusion of your store. The customer feels like they've been handed off to a third party, which erodes trust. I needed a way to keep the customer engaged and informed without me having to act as a human tracking API.

Architecting the Solution

I knew I wanted to build a Shopify app, but the ecosystem can be overwhelming. My main goal was to create a lightweight, easily customizable widget that merchants could drop into their theme without touching complex Liquid code.

The first major decision was how to inject the widget. Shopify's App Embed blocks were the clear winner here. They allow merchants to enable the widget directly from the theme editor, keeping the implementation clean and decoupled from the core theme files.

Next was the backend. I needed to listen to Shopify webhooks to know when an order was fulfilled and extract the tracking details securely. Security was paramount, so I implemented strict HMAC verification for every incoming webhook to ensure the payload actually came from Shopify.

Let's look at the webhook handler. This is the backbone of the tracking state:

const crypto = require('crypto');

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

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'));
    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');
  }
);
Enter fullscreen mode Exit fullscreen mode

The verifyShopifyWebhook function is crucial. It takes the raw body, the HMAC header from Shopify, and your webhook secret, then compares the hashes. If they don't match, the request is rejected. Once verified, we parse the fulfillment data and store it in the database for the frontend to query later.

On the frontend, I used a vanilla JavaScript approach for the widget to keep the bundle size tiny. We pass the order details via data attributes on a simple div, then asynchronously load the tracking script.

{% 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>
Enter fullscreen mode Exit fullscreen mode

This snippet uses Shopify's Liquid variables to pass the context to the widget. The script then initializes the Parcelglance widget, fetching the real-time status from our backend and rendering it inside the container.

Practical Results and Lessons Learned

Building the initial version took a few weekends, but the results were immediate for my friend's store. The most surprising part wasn't the technical execution, but how much the branding mattered. When customers saw a tracking page that matched the store's colors and logo, their anxiety dropped significantly. They stopped emailing support because they could just check the widget themselves.

What worked really well was the App Embed approach. Merchants loved that they didn't have to edit theme.liquid or deal with merge conflicts when updating their theme.

If I were to do it again, I would probably start with a more robust queueing system for the carrier API polling. Initially, I polled carrier APIs on demand, which led to rate limiting issues during traffic spikes. Moving to a background job queue for tracking updates was a necessary pivot that I had to make a few weeks after launch.

Wrapping Up

I ended up turning this internal tool into a public Shopify app called Parcelglance. It provides a branded tracking page, pulls real-time status from major carriers, and integrates seamlessly via App Embed blocks to help reduce those endless WISMO tickets.

If you're dealing with post-purchase support fatigue and want to give your customers a better tracking experience, you can try the free Shipment Tracking Widget at parcelglance.com.


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

Top comments (0)