# Shopify, 3PL & Custom ERP Package Tracking & Webhooks Guide (2026)

URL: https://docs.lastscanned.com/shopify-3pl-wismo-tracking-and-webhooks

Shopify merchants, third-party logistics providers (3PLs), and custom ERP teams typically lose thousands of dollars per year—and hundreds of milliseconds of storefront load time—to legacy post-purchase tracking apps like **AfterShip**, **17TRACK**, and **ParcelPanel**.

Those apps inject multi-script tracking widgets into your storefront theme, lock REST API and webhook access behind **$59–$199/month tiers**, and still fail to resolve **domestic `92…` USPS consolidator gaps** (`OSM Worldwide`, `Pitney Bowes`) or **China cross-border last-mile handoffs** (`Yanwen`, `4PX`, `Cainiao`, `YunExpress` $\rightarrow$ `UniUni`, `SpeedX`, `GOFO`, `OnTrac`).

With **[lastscanned](https://lastscanned.com)**, you can power your Shopify order-status page, 3PL warehouse management system (WMS), Klaviyo/Postscript flows, and Gorgias/Zendesk WISMO tickets using a single synchronous REST API and push **[Watches & Webhooks](/webhooks)** across **[542 carriers](/carriers)** for **$4.99/month** (`1,000` unique tracking numbers included, `$0.015` per additional number, unlimited re-polls and scheduled watch checks).

---

## Architecture Comparison: Storefront Tracking Apps vs. `lastscanned`

| Dimension | Legacy Storefront Apps (AfterShip / 17TRACK / ParcelPanel) | `lastscanned` REST API + Watches + `<lastscanned-widget>` |
|---|---|---|
| **Storefront JavaScript impact** | Heavy third-party scripts, iframes, and promotional upsell banners | Zero third-party trackers; optional `<lastscanned-widget>` is a single lightweight Web Component (`/embed.js`) |
| **Order fulfillment trigger** | Requires async tracker registration; initial API response has `events: []` | **`POST /v1/watches`** returns the live normalized `TrackingResult` immediately *and* schedules automatic webhook updates |
| **Webhook payload format** | Proprietary per vendor | Native `lastscanned` JSON (`tracking.updated`, `watch.ended`) **or drop-in 17TRACK v2 format** (`"format": "17track"`) |
| **`92…` `"USPS Currently Awaiting Package"`** | Stuck in pre-shipment for 3–7 days until USPS local scan | Pass `?carrier=osm` or `?carrier=pitneybowes` on `92…` numbers to surface live middle-mile sort scans |
| **Cross-border last-mile barcode swap** | Customer sees stagnant origin waybill after customs clearance | Automatically extracts destination carrier & local barcode into `message` (e.g., `"Handed to UniUni as UUS5BM1269395824723"`) |
| **Monthly cost at 1,000 orders/mo** | **$59 – $119/month** | **$4.99/month** ([Pro](/plans)) or **$0.02/lookup** via keyless [HTTP 402 Stripe MPP](/authentication#pay-per-request) |

---

## 1. Managing Watches & Webhooks in the Web UI Dashboard (No Code Required)

Before writing any code—or for operations teams managing exception shipments by hand—you can run your entire watch and webhook workflow directly in the **[Web UI Dashboard](https://app.lastscanned.com)**:

- **[Dashboard Watches (`app.lastscanned.com/watches`)](https://app.lastscanned.com/watches)**: Paste up to **100 tracking numbers at once** (with an optional carrier override and custom label stored in `metadata.label`). Each number is looked up immediately, metered under your signed-in account, and displayed in a filterable list (**All**, **Active**, **Ended**) showing its current status, latest scan, and next scheduled check time. Click any watch to inspect its full scan timeline or stop watching with one click.
- **[Dashboard Webhooks (`app.lastscanned.com/webhooks`)](https://app.lastscanned.com/webhooks)**: Set or rotate your webhook endpoint URL and signing secret (`whsec_...`), switch between **native `lastscanned` JSON** and **drop-in 17TRACK v2 format (`"format": "17track"`)**, send a live `webhook.test` ping, and inspect or retry any webhook delivery from the past 30 days.

---

## 2. Triggering a Watch via API When Shopify Creates a Fulfillment (`orders/fulfilled`)

When your Shopify store or 3PL WMS fulfills an order (`orders/fulfilled` or `fulfillments/create` webhook), register the tracking number with `POST https://api.lastscanned.com/v1/watches` and attach the Shopify order ID and customer email in `metadata`:

```ts
// Handler for Shopify's `fulfillments/create` webhook
export async function onShopifyFulfillmentCreate(shopifyFulfillment: {
  order_id: number;
  tracking_number: string;
  tracking_company?: string;
   email?: string;
}) {
  const res = await fetch("https://api.lastscanned.com/v1/watches", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LASTSCANNED_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      trackingNumber: shopifyFulfillment.tracking_number,
      metadata: {
        shopifyOrderId: String(shopifyFulfillment.order_id),
        customerEmail: shopifyFulfillment.email ?? "",
      },
    }),
  });

  const watch = await res.json();
  // watch.id     -> "wat_109060ade9f9a48732117f7a"
  // watch.result -> immediate live TrackingResult (carrier, status, estimatedDelivery, events)
  return watch;
}
```

Because `POST /v1/watches` performs an immediate live lookup and returns `watch.result` in the HTTP response, your database has the normalized carrier slug (`ups`, `usps`, `fedex`, `uniuni`, `speedx`, `veho`, `gofo`, `ontrac`, `yanwen`, `4px`, `cainiao`), `status`, and `estimatedDelivery` on the very first call.

---

## 3. Receiving Signed Status Webhooks (`tracking.updated` & `watch.ended`)

Configure your webhook receiver once via `PUT /v1/webhook` (or on the [Webhooks dashboard](https://app.lastscanned.com/webhooks)):

```bash
curl -X PUT https://api.lastscanned.com/v1/webhook \
  -H "Authorization: Bearer $LASTSCANNED_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://api.yourbrand.com/webhooks/lastscanned"}'
```

`lastscanned` automatically re-checks active watches on an adaptive schedule—**every 15 minutes** when `out_for_delivery`, **every 30 minutes** on `exception`, **every 1 hour** when `in_transit`, and **every 2 hours** on `label_created`—at **zero additional cost**. Whenever the status, newest scan, or `estimatedDelivery` changes, your endpoint receives a signed `POST`:

```ts
import crypto from "node:crypto";

export async function handleLastscannedWebhook(rawBody: string, signatureHeader: string) {
  const { t, v1 } = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
  const expected = crypto
    .createHmac("sha256", process.env.LASTSCANNED_WEBHOOK_SECRET!)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(v1, "hex"), Buffer.from(expected, "hex"))) {
    throw new Error("Invalid Lastscanned-Signature");
  }

  const event = JSON.parse(rawBody);
  if (event.type === "tracking.updated") {
    const { watch, previousStatus, result } = event.data;
    // Example: trigger Klaviyo / Postscript SMS when status transitions to `out_for_delivery` or `exception`
    if (previousStatus !== result.status && result.status === "out_for_delivery") {
      await notifyCustomerOutForDelivery(watch.metadata.shopifyOrderId, result);
    }
  }
}
```

Migrating from an existing **17TRACK** integration? Pass `"format": "17track"` to `PUT /v1/webhook` and `lastscanned` will send `TRACKING_UPDATED` / `TRACKING_STOPPED` payloads with 17TRACK's `sign` header so your existing Shopify/ERP webhook handler works with zero code changes. See **[Migrating from 17TRACK](/migrate-from-17track)**.

---

## 4. Resolving Cross-Border Handoffs & `92…` USPS Consolidator Parcels

For brands shipping from overseas factories (`Yanwen`, `4PX`, `Cainiao`, `YunExpress`) or using domestic consolidators (`OSM Worldwide`, `Pitney Bowes`):

1. **Automatic Last-Mile Handoff Extraction:** When `Yanwen` (`UL494089685YP`), `4PX` (`4PX3002639757438CN`), `Cainiao` (`LP00836938654367`), or `YunExpress` (`YT2621200705470145`) hands a parcel to `UniUni` (`UUS5BM1269395824723`), `SpeedX` (`SPXCLT138701431240`), `GOFO` (`GFUS01074503026309`), or `USPS`, `result.message` surfaces `"Handed to UniUni as UUS5BM1269395824723"`. You can immediately create a second watch on the local courier barcode so the buyer sees real-time out-for-delivery driver scans.
2. **Eliminating `"USPS Currently Awaiting Package"` Tickets:** When a `926129…` or `927489…` barcode returns `"Pre-Shipment: Picked Up by Shipping Partner, USPS Awaiting Item"` on USPS, query `GET /v1/track/9274899998486353272191?carrier=osm` or `GET /v1/track/9261290980949654107812?carrier=pitneybowes` to show the buyer the exact middle-mile sort facility and departure timestamp.

---

## 5. Embedding a Clean, Zero-Bloat Order Lookup Widget

If you want a self-serve tracking lookup page on your Shopify storefront or help center without loading third-party ad scripts, use the [Tracking Widget Generator](https://lastscanned.com/tools/tracking-widget-generator) to drop `<lastscanned-widget>` onto any page:

```html
<script src="https://lastscanned.com/embed.js" defer></script>
<lastscanned-widget
  theme="light"
  accent="#10b981"
  placeholder="Enter your tracking number (e.g. 1Z54V8630394427740)"
></lastscanned-widget>
```

---

## Related Guides & Tools

- **[Watches and Webhooks (`/v1/watches` & `/v1/webhook`)](/webhooks)**
- **[WISMO Helpdesk Integrations (Gorgias, Zendesk, Shopify)](/wismo-integrations)**
- **[Regional & Cross-Border Carrier Tracking API Guide](/regional-carrier-tracking-api-uniuni-speedx-veho-gofo-ontrac)**
- **[AfterShip vs. EasyPost vs. 17TRACK vs. lastscanned (2026)](/aftership-vs-easypost-vs-17track-vs-lastscanned)**
- **[2026 Carrier Surcharges & Rate Guide](/shipping-rates-and-surcharges-2026)**
