
Shipment Tracking
- 153 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Give customers live package tracking by aggregating carrier status updates via webhooks and sending proactive delivery notifications.
About
Aggregates carrier status updates through webhooks and sends proactive delivery notifications for live package tracking. A developer uses it to add order-tracking visibility for customers.
- Aggregates multi-carrier status via webhooks
- Proactive delivery notifications
Shipment Tracking by the numbers
- 153 all-time installs (skills.sh)
- Ranked #2,479 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill shipment-trackingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 153 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Give customers live package tracking by aggregating carrier status updates via webhooks and sending proactive delivery notifications.
Files
Shipment Tracking
Overview
Shipment tracking lets customers follow their package from warehouse to doorstep, and proactively notifies them at key milestones (shipped, out for delivery, delivered). Most platforms send tracking emails automatically when a label is created — but a branded tracking page and proactive exception alerts (lost packages, failed delivery) require a dedicated tracking app or service.
When to Use This Skill
- When customers need real-time package tracking on their order detail pages
- When building post-purchase notifications (email/SMS) triggered by shipping milestone events
- When you need to detect delivery exceptions (lost packages, failed delivery attempts) and trigger customer service alerts
- When aggregating tracking data across multiple carriers (UPS, FedEx, USPS, DHL) into a single interface
- When integrating with a carrier aggregator like EasyPost or Shippo for unified tracking webhooks
Core Instructions
Step 1: Determine your platform and choose the right tracking tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | AfterShip or Route | AfterShip provides a branded tracking page, proactive email/SMS notifications, and exception alerts across all carriers |
| WooCommerce | AfterShip for WooCommerce or Shipment Tracking by WooCommerce | AfterShip integrates with all major carriers; the official WooCommerce extension handles basic carrier tracking |
| BigCommerce | AfterShip (BigCommerce App Marketplace) or ShipStation tracking | AfterShip has a native BigCommerce integration; ShipStation also provides tracking if you use it for label creation |
| Custom / Headless | EasyPost Tracker API or Shippo tracking webhooks | EasyPost and Shippo normalize tracking events across all carriers into a single webhook feed |
Step 2: Set up carrier tracking notifications
Shopify
Shopify's built-in tracking (no extra app needed for basics): 1. When you fulfill an order and add a tracking number, Shopify automatically sends the customer a "Your order is on its way" email with the tracking link 2. The tracking link goes to the carrier's website (e.g., ups.com for UPS) — it's functional but not branded 3. Customers can check their order status at yourstore.com/orders/[order-id]
AfterShip (recommended for branded tracking + proactive notifications): 1. Install AfterShip from the Shopify App Store (free plan available) 2. AfterShip automatically detects new fulfillments in Shopify and begins tracking the package 3. In AfterShip → Notifications, configure email and SMS notifications at each milestone:
- "In transit" — when the package leaves your facility
- "Out for delivery" — day of delivery
- "Delivered" — confirmation
- "Exception" — failed delivery attempt or package delay
4. Customize the tracking page at AfterShip → Tracking Page with your logo, colors, and product recommendations 5. AfterShip's branded tracking page is hosted at yourstore.aftership.com or you can embed it on your own domain
For Shopify Plus: Use Klaviyo to trigger shipping notification emails based on AfterShip tracking events via Klaviyo's AfterShip integration — this gives you full control over the email design and content.
WooCommerce
Using WooCommerce Shipment Tracking (official extension): 1. Install from WooCommerce.com (or use the free community version on WordPress.org) 2. When fulfilling an order, enter the tracking number and carrier in WooCommerce → Orders → [Order] → Add Tracking Number 3. WooCommerce sends the customer an updated order email with the tracking link 4. Customers see the tracking link in My Account → Orders
Using AfterShip for WooCommerce: 1. Install the AfterShip plugin from WordPress.org 2. AfterShip syncs WooCommerce orders and begins tracking when tracking numbers are added 3. Configure notification emails in AfterShip dashboard → Notifications 4. The branded tracking page works the same as the Shopify version
ShipStation tracking (if using ShipStation for fulfillment):
- ShipStation automatically emails customers when a label is created with a tracking link
- Go to ShipStation → Account Settings → Notifications to configure which carrier events trigger emails
BigCommerce
1. Install AfterShip Returns Center or AfterShip from the BigCommerce App Marketplace 2. AfterShip syncs with BigCommerce orders automatically 3. Configure notifications and the branded tracking page in the AfterShip dashboard (same as above)
BigCommerce's built-in tracking:
- BigCommerce automatically sends shipping confirmation emails with tracking links when an order is marked as "Shipped" with a tracking number
- Go to Store Setup → Email Templates → Shipped Email to customize the email content
Step 3: Handle tracking exceptions
Delivery exceptions (lost packages, failed delivery attempts) need fast resolution to protect customer satisfaction.
Setting up exception alerts
AfterShip: 1. Go to AfterShip → Notifications → Exceptions 2. Enable email alerts to your team for these exception types:
- "Exception" — carrier-reported issue
- "Failed Attempt" — delivery attempt failed, needs rescheduling
- "Returned to Sender" — package coming back
3. AfterShip can trigger Slack or email alerts to your ops team alongside the customer notification
Shopify Flow (Plus):
Trigger: AfterShip tracking event received
Condition: Status is "Exception" or "Failed Attempt"
Action: Create customer service task in Gorgias or Zendesk
Action: Send internal Slack notification to #shipping-exceptionsResponding to exceptions
When AfterShip or your tracking tool flags an exception: 1. Check the carrier's native tracking portal for the most current status 2. For lost packages: file a claim with the carrier (UPS, FedEx, USPS all have online claims portals) 3. For failed delivery: contact the customer to update their delivery address or arrange pickup 4. For returned packages: coordinate with the customer on re-shipping vs. refund
Step 4: Add a branded tracking page to your store
A branded tracking page keeps customers on your site (vs. carrier sites), allows product recommendations, and reduces "where is my order" contacts.
AfterShip generates this automatically at yourstore.aftership.com. To put it on your own domain:
1. In AfterShip → Tracking Page → Custom Domain, enter your subdomain (e.g., tracking.yourstore.com) 2. Add a CNAME DNS record pointing tracking.yourstore.com to AfterShip's servers 3. Update your shipping confirmation email to link to tracking.yourstore.com/[tracking-number] instead of the carrier URL
Embed tracking in your existing order status page:
- For Shopify: AfterShip has a theme app extension that embeds the tracking widget directly in the Shopify order status page
- For WooCommerce: the AfterShip plugin adds a tracking section to the WooCommerce "My Account → Orders" page automatically
Step 5: Custom / Headless — webhook-based tracking
For headless implementations, use EasyPost or Shippo to receive carrier webhooks and normalize tracking events:
// Register a tracking number with EasyPost to receive webhook updates
async function registerEasyPostTracker(params: {
trackingNumber: string;
carrier: string; // 'UPS', 'FedEx', 'USPS', 'DHL'
orderId: string;
}): Promise<void> {
const response = await fetch('https://api.easypost.com/v2/trackers', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.EASYPOST_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
tracker: {
tracking_code: params.trackingNumber,
carrier: params.carrier,
},
}),
});
const tracker = await response.json();
// Store tracker.id to correlate future webhook events with this order
await db.shipments.update({ tracking_number: params.trackingNumber }, {
easypost_tracker_id: tracker.id,
});
}
// Receive EasyPost webhook and update order status
// POST /webhooks/easypost
async function handleEasyPostWebhook(rawBody: Buffer, signature: string): Promise<void> {
// Verify webhook signature
const expectedSig = crypto
.createHmac('sha256', process.env.EASYPOST_WEBHOOK_SECRET!)
.update(rawBody)
.digest('hex');
if (expectedSig !== signature) throw new Error('Invalid webhook signature');
const event = JSON.parse(rawBody.toString());
if (event.description !== 'tracker.updated') return;
const tracker = event.result;
const shipment = await db.shipments.findByEasyPostTrackerId(tracker.id);
if (!shipment) return;
// Normalize EasyPost status to your internal status
const statusMap: Record<string, string> = {
pre_transit: 'label_created',
in_transit: 'in_transit',
out_for_delivery: 'out_for_delivery',
delivered: 'delivered',
failure: 'exception',
return_to_sender: 'returned',
};
const normalizedStatus = statusMap[tracker.status] ?? 'in_transit';
await db.shipments.update(shipment.id, { status: normalizedStatus });
// Update order to delivered when package arrives
if (normalizedStatus === 'delivered') {
await db.orders.update(shipment.order_id, { status: 'delivered', delivered_at: new Date() });
}
// Send notification email at key milestones
if (['in_transit', 'out_for_delivery', 'delivered', 'exception'].includes(normalizedStatus)) {
await sendTrackingNotificationEmail(shipment.order_id, normalizedStatus, tracker.tracking_code);
}
}Best Practices
- Use webhooks over polling — carrier webhooks deliver updates in near-real-time; polling every few hours misses same-day events like "out for delivery" and "delivered"
- Send proactive notifications for exceptions — a delay or failed delivery email from you, before the customer notices, transforms a bad situation into a positive brand moment
- Show estimated delivery date from day one — AfterShip and most carriers provide an EDD when the label is first scanned; display it on the tracking page and order confirmation email immediately
- Alert your ops team on exceptions, not just customers — create an internal Slack/email alert for every exception event so your team can proactively resolve issues
- Deduplicate notification emails — carriers sometimes send duplicate events; ensure your tracking app or custom webhook handler deduplicates based on event type per shipment
Common Pitfalls
| Problem | Solution |
|---|---|
| Customer doesn't receive shipping confirmation | Check that Shopify/WooCommerce shipping notification emails are enabled and that the customer's email address was captured at checkout |
| Tracking page shows "no information available" for days | This is normal for USPS for 24–48 hours after label creation; set customer expectations in the shipping confirmation email |
| Exception events not triggering alerts | Verify your AfterShip notification settings include exception types; test by using a test tracking number that simulates an exception event |
| International tracking stops updating mid-journey | International handoffs between carriers often cause gaps; log the carrier's native tracking URL and fall back to it when events stop for 48+ hours |
Related Skills
- @order-fulfillment-workflow
- @returns-management
- @international-shipping
- @order-management-system
- @same-day-delivery
{
"context": "Tests whether the agent sends notifications only for the correct subset of statuses, deduplicates per order/status via a notification log, uses the correct named email templates, builds carrier-specific tracking URLs with correct URL patterns, and triggers a Slack/internal alert specifically for exception events.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Notify only 4 statuses",
"max_score": 10,
"description": "sendTrackingNotification only sends emails for: in_transit, out_for_delivery, delivered, exception — and skips pre_transit and returned (returns early or does not send for those statuses)"
},
{
"name": "Deduplication check",
"max_score": 10,
"description": "Before sending, the code checks a notification log (db.notificationLog or equivalent) to see if a notification for this order+status was already sent"
},
{
"name": "Dedup record insert",
"max_score": 8,
"description": "After sending, the code inserts a record into the notification log with the order_id and status (e.g. tracking_in_transit, tracking_delivered) to prevent future duplicates"
},
{
"name": "in_transit template name",
"max_score": 7,
"description": "Uses 'shipment-in-transit' as the email template name for in_transit status (not a generic or differently-named template)"
},
{
"name": "out_for_delivery template name",
"max_score": 7,
"description": "Uses 'shipment-out-for-delivery' as the email template name for out_for_delivery status"
},
{
"name": "delivered template name",
"max_score": 7,
"description": "Uses 'shipment-delivered' as the email template name for delivered status"
},
{
"name": "exception template name",
"max_score": 7,
"description": "Uses 'shipment-exception' as the email template name for exception status"
},
{
"name": "UPS tracking URL",
"max_score": 6,
"description": "buildTrackingUrl returns a URL containing 'ups.com/track' (or equivalent UPS tracking domain) for carrier 'ups'"
},
{
"name": "FedEx tracking URL",
"max_score": 6,
"description": "buildTrackingUrl returns a URL containing 'fedex.com' for carrier 'fedex'"
},
{
"name": "USPS tracking URL",
"max_score": 6,
"description": "buildTrackingUrl returns a URL containing 'usps.com' for carrier 'usps'"
},
{
"name": "Unknown carrier fallback",
"max_score": 6,
"description": "buildTrackingUrl returns a fallback URL (e.g. Google search or similar) for unknown/unrecognized carrier names rather than throwing an error"
},
{
"name": "Exception Slack alert",
"max_score": 10,
"description": "For exception status events, the code triggers an internal Slack (or equivalent internal) alert/notification — separate from or in addition to the customer email"
},
{
"name": "Design doc covers statuses",
"max_score": 5,
"description": "notification-design.md lists which statuses trigger customer emails and which trigger internal alerts (exception)"
},
{
"name": "Design doc covers dedup",
"max_score": 5,
"description": "notification-design.md explains the deduplication mechanism (notification log / one per status per order)"
}
]
}
Post-Purchase Shipping Notifications
Problem/Feature Description
An e-commerce company's customer service team is overwhelmed with "where is my order?" tickets. The engineering team wants to build a notification system that proactively emails customers as their packages hit key milestones in the delivery journey. The support team also reports that when packages are lost or delivery fails, they find out from the customer rather than from an automated alert — which delays resolution by hours.
The backend already receives normalized tracking events (with fields: trackingNumber, carrier, status, occurredAt). The team needs a TypeScript module that, given a normalized tracking event and an order ID, sends the appropriate customer email and (when things go wrong) fires an internal alert. The system should not send duplicate emails if the same status fires more than once for the same order — this has happened before due to carrier retransmissions.
Output Specification
Produce a TypeScript file notification-service.ts that exports:
- A
sendTrackingNotification(orderId: string, event: NormalizedTrackingEvent)function - A
buildTrackingUrl(carrier: string, trackingNumber: string)function
Use stub objects for db, emailService, and slackService (define them as simple constants — no real implementation needed) so the logic is fully self-contained and reviewable.
Also produce notification-design.md that documents which statuses trigger customer emails, which trigger internal alerts, and how duplicate notifications are prevented.
{
"context": "Tests whether the agent implements the correct database schema with proper status enum, unique constraints, and indexes; normalizes carrier events through a STATUS_MAPPING; preserves carrier-native codes; uses ON CONFLICT DO NOTHING for idempotency; and updates the order to 'delivered' when a shipment is delivered.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Status enum values",
"max_score": 8,
"description": "The shipments table status CHECK constraint (or equivalent) includes exactly these values: 'pre_transit', 'in_transit', 'out_for_delivery', 'delivered', 'exception', 'returned'"
},
{
"name": "Unique shipment index",
"max_score": 8,
"description": "schema.sql includes a UNIQUE index or constraint on (carrier, tracking_number) on the shipments table"
},
{
"name": "Tracking events index",
"max_score": 6,
"description": "schema.sql includes an index on tracking_events(shipment_id, occurred_at) — with occurred_at DESC or equivalent — for efficient event retrieval"
},
{
"name": "STATUS_MAPPING present",
"max_score": 8,
"description": "tracking-processor.ts exports or defines a STATUS_MAPPING object that maps carrier codes (e.g. 'failure', 'return_to_sender') to normalized status strings"
},
{
"name": "failure maps to exception",
"max_score": 7,
"description": "STATUS_MAPPING maps 'failure' to 'exception' (not 'failed' or other value)"
},
{
"name": "return_to_sender maps to returned",
"max_score": 7,
"description": "STATUS_MAPPING maps 'return_to_sender' to 'returned'"
},
{
"name": "carrierCode stored",
"max_score": 8,
"description": "normalizeEasyPostEvent or the event insertion preserves the carrier's native status code (e.g. as carrierCode or carrier_code field) alongside the normalized status"
},
{
"name": "ON CONFLICT DO NOTHING",
"max_score": 12,
"description": "The INSERT for tracking_events uses ON CONFLICT DO NOTHING (or equivalent upsert that skips duplicates) to handle repeated event delivery idempotently"
},
{
"name": "Unique conflict target",
"max_score": 8,
"description": "The ON CONFLICT clause targets (shipment_id, carrier_code, occurred_at) or equivalent unique combination to detect duplicates"
},
{
"name": "Order delivered update",
"max_score": 12,
"description": "processTrackerEvent updates the orders table (status to 'delivered' and sets a delivered_at/delivered timestamp) when the latest event status is 'delivered'"
},
{
"name": "actual_delivery set",
"max_score": 8,
"description": "processTrackerEvent sets actual_delivery on the shipments record to the event's occurredAt when status is 'delivered'"
},
{
"name": "latest event by timestamp",
"max_score": 8,
"description": "processTrackerEvent determines the current status by sorting/selecting the event with the latest occurred_at (not just the first element or arbitrary order)"
}
]
}
Shipment Tracking Data Layer
Problem/Feature Description
A growing e-commerce platform is adding multi-carrier package tracking to their order management system. The team needs to design the database schema and the core data processing layer that takes raw tracking events from their carrier aggregator and persists them reliably. The aggregator sends batches of tracking details for a given shipment — and the same events may be resent multiple times when the aggregator retries failed deliveries.
The platform uses PostgreSQL and a TypeScript/Node.js backend. The head of engineering wants to make sure that running the same webhook payload twice doesn't create duplicate events in the database, and that when a package is delivered the order record is automatically updated so the fulfillment team doesn't have to manually close out orders.
Output Specification
Produce two files:
1. schema.sql — PostgreSQL DDL creating the tables and indexes required to store shipment and tracking data. Include all necessary constraints.
2. tracking-processor.ts — A TypeScript module that exports:
- A carrier status normalization mapping (translating carrier-specific codes to your unified status vocabulary)
- A
normalizeEasyPostEvent(trackerData: any)function - A
processTrackerEvent(trackerData: any)function that persists events and updates the shipment and order
Use a db stub object (define it as a simple constant with the methods called — no need to implement the actual DB layer) so the logic is self-contained and reviewable.
{
"context": "Tests whether the agent correctly implements an EasyPost webhook receiver with proper HMAC signature verification using the raw request body, immediately acknowledges the webhook with 200, and offloads processing to an async queue rather than doing it inline.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Raw body middleware",
"max_score": 12,
"description": "Uses express.raw() (not express.json()) as the body parser middleware on the webhook route — the middleware call includes express.raw or bodyParser.raw, NOT express.json"
},
{
"name": "HMAC SHA-256 verification",
"max_score": 10,
"description": "Constructs an HMAC using the 'sha256' algorithm (crypto.createHmac('sha256', ...)) to verify the incoming signature"
},
{
"name": "Signature header check",
"max_score": 10,
"description": "Reads the signature from the 'x-hmac-signature' request header and compares it to the computed HMAC digest"
},
{
"name": "401 on invalid signature",
"max_score": 8,
"description": "Returns a 401 status code (or equivalent rejection) when the computed signature does NOT match the received signature"
},
{
"name": "Webhook secret from env",
"max_score": 8,
"description": "Reads the webhook secret from an environment variable (e.g. process.env.EASYPOST_WEBHOOK_SECRET or similar) rather than hardcoding it"
},
{
"name": "Filter tracker.updated events",
"max_score": 10,
"description": "Checks event.description === 'tracker.updated' (or equivalent) before processing, to ignore non-tracker event types"
},
{
"name": "Immediate 200 response",
"max_score": 12,
"description": "Sends a 200 response (e.g. res.json({ received: true }) or similar) BEFORE or without waiting for the async queue/processing to complete"
},
{
"name": "Async queue offload",
"max_score": 12,
"description": "Calls a queue (queue.add, worker.enqueue, or any background job mechanism) to process the event asynchronously, rather than processing inline in the request handler"
},
{
"name": "Notes mention async rationale",
"max_score": 9,
"description": "implementation-notes.md explains that the 200 is returned immediately to prevent webhook timeouts or missed events (or equivalent rationale)"
},
{
"name": "Notes mention signature rationale",
"max_score": 9,
"description": "implementation-notes.md mentions that raw body parsing is required for HMAC verification (or equivalent note about why express.json cannot be used)"
}
]
}
Carrier Webhook Receiver Service
Problem/Feature Description
A mid-size e-commerce company has recently contracted with EasyPost to handle multi-carrier shipping across their order management platform. The engineering team needs to integrate EasyPost's webhook feed to receive real-time tracking status events as packages move through carrier networks. Currently, the backend team polls carrier APIs on a schedule, which causes rate-limit errors during peak shipping seasons and means customers sometimes see stale tracking data for hours.
The team wants a new Express.js webhook endpoint that receives events from EasyPost, validates their authenticity, and hands them off for background processing. The endpoint will live alongside other existing routes in a Node.js/TypeScript backend and needs to be robust enough to handle high-volume traffic without dropping events.
Output Specification
Produce a TypeScript file webhook-handler.ts that implements an Express route handler for POST /webhooks/easypost. The implementation should:
- Correctly handle raw body parsing for signature verification
- Validate the incoming request's authenticity
- Filter to only relevant event types
- Return a response immediately and defer processing to a queue
- Include a stub
processTrackerEventfunction and aqueuestub (can be a simple object with anaddmethod) so the code is self-contained and reviewable
Also produce a short implementation-notes.md explaining the key design decisions made in the implementation, particularly around security and reliability.
{
"name": "finsi/shipment-tracking",
"version": "0.1.0",
"summary": "Track shipments across carriers with webhook-driven status updates",
"skills": {
"shipment-tracking": {
"path": "SKILL.md"
}
}
}