When I first started helping out with a small Shopify store, I thought the hardest part would be getting the initial sales. I was wrong. The real nightmare began a few weeks after launch, when the support inbox started flooding.
It wasn't complaints about product quality. It was almost entirely WISMO—"Where Is My Order?"—tickets. I was spending hours every single day manually looking up tracking numbers, copying them, and pasting them into email replies. I was struggling to keep up, and my response times were slipping. I was spending more time acting as a human tracking API than actually growing the business. So I decided to build something.
The Post-Purchase Black Hole
The post-purchase experience in e-commerce is often a complete black hole. A customer clicks "buy," gets a standard confirmation email, and then... nothing. They wait. They get anxious. They check their spam folder. Finally, they email your support team.
Ignoring this problem has a very real cost. Let's look at the math for a moment. For example, if you receive just 20 WISMO tickets a day, and each takes 5 minutes to research and reply to, that's over 1.5 hours of pure productivity lost every single day. Multiply that by your hourly rate or the time you could be spending on marketing, and it adds up fast. Beyond the time sink, it creates a frustrating customer experience. If they have to ask where their package is, we've already failed to keep them engaged. We needed a way to automate this entirely and bring the tracking data directly to the customer.
Automating the Tracking Experience
My goal was to build a lightweight, branded tracking widget that could live directly on the Shopify order status page. I didn't want to force merchants to edit complex theme code, so I leaned heavily into Shopify's App Embed blocks.
The architecture needed two main pieces: a frontend widget to display the tracking data, and a backend listener to keep the fulfillment data up to date in real time.
First, let's look at the frontend. We need to inject the widget into the order status page and pass the necessary order context from Shopify's Liquid templates into our JavaScript.
{% 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>
This Liquid snippet creates a container and uses data attributes to safely pass the order.id, order.name, and shop.name to the client-side script. The script then dynamically loads our tracking widget, keeping the initial page load fast. The widget fetches the real-time status from our backend and renders a branded timeline that matches the store's theme.
But a frontend widget is only as good as the data feeding it. To ensure the tracking status is always accurate, we need to listen to Shopify's fulfillment webhooks. This is where the backend automation comes in.
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');
}
);
Security is critical here. The verifyShopifyWebhook function ensures that the payload actually came from Shopify by comparing the HMAC SHA-256 hash. Once verified, we parse the JSON and upsert the tracking number and carrier into our database. This means the moment a merchant marks an order as fulfilled in Shopify, our backend instantly knows the tracking details. When the customer visits their order status page, the frontend widget queries this database and displays the real-time status without any manual intervention.
Practical Results and Lessons Learned
The shift in productivity was immediate. By automating the tracking visibility, the WISMO tickets dropped significantly. Customers could see exactly where their package was, complete with a branded timeline, right on the order status page.
What surprised me the most was the secondary effect on post-purchase engagement. Because the tracking page was branded and visually appealing, customers actually spent time looking at it. It transformed a boring waiting period into a cohesive part of the store's experience. Some merchants even noticed a slight bump in repeat purchases, as customers could easily review their past orders through the same interface.
If I were to change anything about the initial build, it would be how I handled the theme integration. At first, I tried to build a fully custom theme section that required merchants to paste code into their theme.liquid or order template files. It was a headache for them and a nightmare for support. Switching to Shopify App Embed blocks was a huge relief. It allowed merchants to toggle the widget on and off with a single click, completely decoupling the tracking logic from their core theme files.
Wrapping Up
If you are running a Shopify store and drowning in WISMO tickets, you don't need to build this from scratch. I packaged the core functionality into a free tool to help other developers and store owners automate their post-purchase experience. You can grab the Shipment Tracking Widget and see how it works at parcelglance.com.
Full disclosure: I'm the developer of Parcelglance. I built this because I needed it myself.
Top comments (0)