I still remember the exact moment I realized how much time I was wasting on support emails. I was helping a friend manage her Shopify store, and we were drowning in "Where is my order?" (WISMO) messages. We’d spend hours manually checking carrier websites, copying tracking numbers, and pasting them into email replies. It was exhausting, unscalable, and frankly, a terrible customer experience. The post-purchase phase felt like a black hole for the buyer.
So I decided to build something.
The Problem with the Post-Purchase Black Hole
The problem with WISMO isn't just the volume of support tickets; it's the erosion of customer trust. When a customer clicks "Buy," they enter a state of high anticipation. If the default Shopify order status page doesn't provide a seamless, branded tracking experience, that anticipation quickly turns into anxiety.
Ignoring the post-purchase experience has a real cost. For example, if a store processes 100 orders a day and just 5% of customers email support about their shipping status, that’s 5 extra hours of work daily. Multiply that by the cost of support staff or the founder's time, and it adds up fast. Plus, frustrated customers are more likely to leave negative reviews or never return. I needed a way to keep customers engaged and informed without manual intervention.
Technical Approach and Trade-offs
Building Parcelglance started with a lot of trial and error. My first mistake was overcomplicating the architecture. I initially tried to build a massive, standalone tracking portal that required users to leave the store. It felt clunky and disconnected from the brand. I quickly pivoted to using Shopify’s Theme App Extensions (App Embed blocks), which allowed me to inject a tracking widget directly into the merchant's existing order status page.
Here is the core Liquid and JavaScript snippet I use to render the widget. It securely passes order data via HTML attributes and asynchronously loads 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>
This approach was a game-changer for installation. Merchants just enable the App Embed block in their theme settings, and the widget appears. No theme code editing required.
However, the backend presented its own learning curve. Handling Shopify webhooks for fulfillment updates was my second major stumbling block. Early on, I didn't properly verify the HMAC signatures, which is a massive security risk. I also tried to process the webhook payloads synchronously, which caused timeouts when the database was slow.
Here is the corrected Node.js webhook handler I use today. It verifies the request and safely upserts the fulfillment data:
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');
}
);
Writing the HMAC verification from scratch taught me a lot about Shopify's security model. It’s crucial to always validate the raw body against the header before parsing or processing anything.
Practical Results and Retrospective
Looking back, the App Embed approach was definitely the right call. What worked better than expected was the focus on visual customization. I initially thought merchants just wanted the raw data. I was wrong. They wanted the widget to match their store's exact typography and colors. Spending extra time on CSS variables and theme-aware styling made a huge difference in adoption.
What surprised me was how much the post-purchase experience impacts perceived shipping speed. Even if the physical shipping time is the exact same, a transparent, real-time tracking page makes the wait feel shorter and reduces anxiety.
If I were to do it again, I’d handle carrier API edge cases much earlier. Regional carriers often have weird, non-standard tracking statuses that break standard parsing logic. I spent weeks patching these edge cases after launch. I would also set up a more robust queuing system for webhook processing from day one, rather than relying on simple async/await blocks.
Moving Forward
Building this tool solved my immediate problem, but I realized other Shopify merchants were facing the exact same WISMO headaches. That’s why I packaged it into Parcelglance. It’s a free shipment tracking widget that adds a branded, real-time tracking page to your store, helping you reduce support tickets and keep customers engaged post-purchase.
You can check out the free tool 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)