DEV Community

seller-mind
seller-mind

Posted on

How I Architected a Post-Purchase Tracking Widget for Shopify

When I first launched my Shopify store, I genuinely thought the hardest part would be getting the first sale. I was wrong. The real nightmare started after the orders actually rolled in. Suddenly, my inbox was flooded with "Where is my order?" emails. I was spending two hours every single morning manually checking carrier websites, copying tracking numbers, and replying to anxious customers. It was incredibly draining, and I knew it was frustrating for them, too. I realized my post-purchase experience was completely broken. So I decided to build something.

The Problem with Post-Purchase

The post-purchase experience is often an afterthought in e-commerce. We spend weeks optimizing the checkout flow, designing beautiful product pages, and tweaking the cart drawer. But once the customer clicks "Pay," we just hand them off to a generic carrier tracking page.

The problem is that generic carrier pages are clunky, often ad-filled, and completely disconnected from your brand. When customers can't easily find their tracking info in a familiar environment, they email you. For example, it's common for WISMO (Where Is My Order) tickets to account for a massive chunk of your daily support queue.

Ignoring this means paying for support hours that could be spent on product development or marketing. More importantly, it risks customer trust. If they can't track their package easily, they assume something went wrong. I needed a way to keep them engaged and informed without me having to act as a manual tracking proxy.

The Technical Approach

To solve this, I needed a way to inject a branded tracking widget directly into the order status page. Initially, I looked at requiring merchants to edit their theme code manually, but that creates too much friction. Shopify's App Embed blocks were the perfect solution for this, allowing a seamless, code-free installation for the end user.

Building the App Embed

The frontend needed to be lightweight and dynamically pull in the order details from the Shopify Liquid context. Here is the core snippet I used for the App Embed block:

{% 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

By passing the order.id and order.name via data attributes, the external JavaScript file can securely request the tracking timeline from our backend without exposing sensitive customer data in the DOM. The min-height style prevents layout shift (CLS) while the script loads.

Handling Webhooks Securely

The frontend is only half the battle. We need real-time tracking data, which means we need to listen to Shopify's fulfillment events. When an order is fulfilled, Shopify sends a webhook. Because this data is sensitive, verifying the webhook's authenticity is critical.

Here is the Node.js/Express handler I wrote to process the 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');
  }
);
Enter fullscreen mode Exit fullscreen mode

The verifyShopifyWebhook function is non-negotiable. It ensures the payload actually came from Shopify. Once verified, we parse the raw body and upsert the tracking number and carrier into our database. From there, a background worker polls the major carrier APIs to update the status, which the frontend widget then fetches.

Practical Results and Lessons Learned

The biggest win was seeing the support ticket volume drop naturally. Customers actually enjoyed the cohesive, branded experience. It felt like a natural extension of the store rather than a jarring redirect to a shipping company's website.

One surprise was how much the App Embed feature simplified the onboarding process. Initially, I was worried about the complexity of injecting scripts into the order status page, but App Embeds made it a straightforward configuration.

If I were to architect this again, I'd spend more time optimizing the initial load time of the widget script. Carrier API responses can sometimes add latency to the tracking timeline. I ended up implementing a local caching layer for the tracking states to mitigate this, but it's an ongoing balancing act between data freshness and performance.

Wrapping Up

Building this completely changed how I handle post-purchase communication. If you're dealing with WISMO tickets and want a simple way to add a branded tracking experience to your store, I've packaged this solution into a free tool. You can check out the Shipment Tracking Widget at parcelglance.com. It handles the real-time carrier updates and lets you customize the widget to match your store's theme.


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

Top comments (0)