DEV Community

seller-mind
seller-mind

Posted on

Taming Post-Purchase Chaos: Building a Shopify Tracking Widget

When I first started helping a friend manage their Shopify store, I thought the hard part was getting the initial sales. I was wrong. The real headache started the moment an order was placed. About a week later, my inbox would flood with the same question: "Where is my order?" (WISMO). At first, I replied to each one personally. But when we hit a minor shipping delay and I received 40 identical tickets in a single afternoon, I realized our post-purchase experience was completely broken. So I decided to build something.

The Hidden Cost of WISMO

The WISMO problem is a silent killer for small e-commerce brands. It’s not just about the annoyance of clearing out an inbox. Every support ticket takes time. If a ticket takes, for example, five minutes to investigate and reply to, 40 tickets is over three hours of lost productivity.

But the hidden cost is much higher than just time. When customers have to dig for tracking info, or worse, get redirected to a generic, ad-filled carrier page, they feel disconnected from your brand. Trust erodes. They don't come back. Ignoring the post-purchase experience means you're essentially leaving customer loyalty on the table after they've already checked out. We needed a way to keep the customer engaged on our own site with real-time, branded tracking updates.

The Technical Approach

The main challenge was building a tracking widget without requiring merchants to manually edit their Liquid theme files. Asking store owners to dig into theme.liquid is a massive barrier to entry and a recipe for support nightmares when themes update.

I decided to build the tracking widget using Shopify’s App Embed blocks. This allows the widget to be injected dynamically via the theme editor, keeping the installation process completely frictionless.

Here is how the frontend integration looks. We use Liquid to pass the necessary order context into the DOM, and 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 approach keeps the initial page load fast because the tracking script is only fetched when the widget container is actually rendered. The Parcelglance.init function takes the order details and fetches the specific tracking state for that customer. One trade-off with App Embeds is CSS specificity; you have to be very careful to scope your widget's CSS so it doesn't accidentally bleed into the merchant's existing theme styles.

But a frontend widget is useless without accurate, real-time data. To get the shipment status from major carriers into our system, we rely on Shopify webhooks. When an order is fulfilled, Shopify sends a payload to our endpoint. Security is critical here, so we verify the HMAC signature to ensure the request actually came 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;
}

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

By parsing the fulfillment data from the webhook, we extract the tracking number and carrier. This allows our system to poll the respective carrier APIs for real-time status updates, which the frontend widget then displays to the customer. It’s a straightforward pipeline, but getting the data ingestion and frontend rendering to talk to each other seamlessly took some careful architectural planning.

Practical Results and Learnings

Once we rolled out the App Embed widget, the shift in the support queue was noticeable. Because the tracking page matched the store's theme and provided clear, real-time status updates, customers felt informed. They didn't need to email us to ask if their package had moved.

What surprised me most was how much a simple UI change altered customer perception. People assume a package is lost if they can't see it moving. By providing a branded, transparent tracking experience, we turned a moment of anxiety into a moment of post-purchase engagement.

If I were to rebuild this today, I would probably spend more time optimizing the CSS-in-JS payload. The widget is lightweight, but shaving off a few more kilobytes on the initial script load would make it even snappier on slower mobile connections. I'd also look into adding more granular theme-matching options, allowing merchants to pass specific hex codes directly from their Shopify theme settings into the widget's initialization payload.

Try It Yourself

If you're running a Shopify store and dealing with WISMO tickets, you don't need to build this from scratch. I packaged the solution into a free tool called Parcelglance. It adds a branded, customizable shipment tracking widget to your store via App Embed blocks, helping you reduce support tickets and keep customers engaged post-purchase.

You can check it out and add the free Shipment Tracking Widget 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)