When I first started helping a friend manage their Shopify store, I assumed the hard part was just getting the initial traffic and securing the first sale. I was wrong. The real headache actually started after the checkout. My inbox was quickly flooded with "Where is my order?" emails. I was struggling to keep up with manual tracking updates, copy-pasting carrier links into support replies, and calming down anxious customers. It was draining my time, causing burnout, and honestly hurting the brand's reputation. So I decided to build something.
The High Cost of the Post-Purchase Void
WISMO (Where Is My Order) isn't just a minor annoyance; it's a massive, hidden drain on e-commerce resources. When customers don't know where their package is, they don't just sit quietly and wait. They email support, they check their spam folders, they call the carrier, and in worst-case scenarios, they initiate chargebacks because they assume the package was lost.
For a small team or a solo developer, handling these support tickets manually is completely unsustainable. But beyond the support costs, ignoring the post-purchase experience means losing control of the narrative. If you aren't proactively communicating shipping status, the carrier's generic, unbranded email is the last thing the customer sees. That's a missed opportunity for engagement and a guaranteed spike in support overhead. I needed a way to keep the customer in the loop without adding to my manual workload.
The Technical Approach: App Embeds and Webhooks
I needed a solution that was native to Shopify, didn't slow down the storefront, and looked like it naturally belonged there. After evaluating a few approaches, I decided to use Shopify's App Embed blocks. This allows the tracking widget to be injected seamlessly into the theme without requiring merchants to manually edit their core theme code or deal with messy script injections.
Here is the frontend Liquid and JavaScript snippet I used for the App Embed block. It dynamically pulls the order details and initializes the tracking widget:
{% 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>
The key decision here was loading the widget script asynchronously. By creating the script element dynamically and setting script.async = true, we ensure that fetching the tracking widget doesn't block the rendering of the rest of the order status page. This kept the store's Core Web Vitals completely intact.
On the backend, keeping the tracking data in sync with the carriers required a robust webhook architecture. I initially considered polling the carrier APIs every few minutes, but that was inefficient and prone to rate limits. Instead, I set up Shopify webhooks to listen for fulfillment updates.
Here is the Node.js webhook handler I wrote to process those updates securely:
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 was a major priority here. The verifyShopifyWebhook function ensures that the payload actually came from Shopify by comparing the HMAC SHA-256 hash of the raw body against the header provided by Shopify. Once verified, the fulfillment data is upserted into the database. This gives the frontend widget a reliable, single source of truth to query when a customer visits their order status page.
Practical Results and Lessons Learned
The App Embed approach worked beautifully because it kept the theme files clean and made the installation process trivial for the end user. What surprised me the most, however, was the psychological impact of the branded experience. Just seeing the store's logo, colors, and custom messaging on the tracking page seemed to significantly reduce customer anxiety.
By providing a customizable tracking widget that matches the store theme, the post-purchase experience feels like a natural continuation of the brand, rather than a hand-off to a third-party logistics company. This directly translated to a noticeable drop in WISMO support tickets.
If I were to rebuild this today, I'd implement a more robust retry and dead-letter queue mechanism for the webhooks right out of the gate. While Shopify's webhook delivery is generally reliable, it can occasionally drop events under extreme high load, and handling those edge cases gracefully is critical for data consistency.
Try It Yourself
I ended up packaging this exact logic and architecture into a free tool called Parcelglance. It's a Shopify post-purchase shipment tracking widget that adds a fully branded tracking page to your store, pulling real-time status from major carriers. It installs easily via Shopify App Embed blocks, allowing you to reduce WISMO tickets and improve post-purchase engagement without touching your theme code.
You can grab the free 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)