This is a focused setup guide for the WhereParcel multi-carrier tracking API. The goal: from zero to a working production-shaped integration in five minutes, with code examples in four languages and a clear production checklist at the end.

If you just want a TL;DR, jump to the setup checklist at the bottom.

What You’ll Have at the End

  • An API key tested against a live request
  • A multi-carrier lookup that works across USPS, UPS, FedEx, DHL, and 60+ live carriers (more on request)
  • Auto-detection so you don’t hardcode carrier names
  • A webhook listener for push status updates
  • A production-ready error and retry pattern

Step 1 — Sign Up and Get Your Key (1 min)

Go to whereparcel.com, sign up with email, and open your dashboard.

Create a new API key from /dashboard/api-keys. Your key looks like:

wp_live_a1b2c3d4e5f6g7h8i9j0

Treat it like a password. Never put it in client-side JavaScript or commit it to a public repo. Use environment variables:

export WP_API_KEY="wp_live_a1b2c3d4e5f6g7h8i9j0"

The 7-day free trial activates immediately, so you can test against the real API. If you want extended free usage, the ambassador program gives you 3 months of the Starter plan free for one short blog post mentioning WhereParcel.

Step 2 — Your First Tracking Request (2 min)

The single endpoint you need is POST /v2/track. Pick the language you actually use.

cURL

curl -X POST https://api.whereparcel.com/v2/track \
  -H "Authorization: Bearer $WP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "trackingItems": [
      { "carrier": "us.usps", "trackingNumber": "9400111899223197428490" }
    ]
  }'

Node.js (fetch)

const res = await fetch('https://api.whereparcel.com/v2/track', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.WP_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    trackingItems: [
      { carrier: 'us.usps', trackingNumber: '9400111899223197428490' },
    ],
  }),
});
const data = await res.json();
console.log(data);

Python (requests)

import os, requests

res = requests.post(
    'https://api.whereparcel.com/v2/track',
    headers={'Authorization': f"Bearer {os.environ['WP_API_KEY']}"},
    json={
        'trackingItems': [
            {'carrier': 'us.usps', 'trackingNumber': '9400111899223197428490'}
        ]
    },
)
print(res.json())

Go (net/http)

body := strings.NewReader(`{
    "trackingItems": [
        {"carrier": "us.usps", "trackingNumber": "9400111899223197428490"}
    ]
}`)
req, _ := http.NewRequest("POST", "https://api.whereparcel.com/v2/track", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("WP_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

What You Get Back

{
  "success": true,
  "data": [
    {
      "carrier": "us.usps",
      "carrierName": "USPS",
      "trackingNumber": "9400111899223197428490",
      "status": "in_transit",
      "lastUpdated": "2026-05-04T12:00:00Z",
      "events": [
        { "time": "2026-05-04T12:00:00Z", "location": "Los Angeles, CA", "description": "Out for delivery" }
      ]
    }
  ]
}

The schema is identical across all 64+ live carriers (and new ones added on request). Status enums are normalized — no more switching on each carrier’s quirky strings.

Step 3 — Auto-Detection (3 min)

You usually don’t know which carrier owns a number until you ask. Pass carrier: "auto" and the API figures it out:

{
  trackingItems: [
    { carrier: 'auto', trackingNumber: '1Z999AA10123456784' }, // UPS
    { carrier: 'auto', trackingNumber: 'EE123456785US' },      // USPS S10
    { carrier: 'auto', trackingNumber: 'JJD01234567890' },     // DHL
  ]
}

The response tells you which carrier was matched. This single feature is usually the reason teams switch from carrier-specific APIs to a unified tracking API — there’s no longer any per-carrier branching code in your codebase.

You can also batch up to 5 numbers per request, which is the right move for any background job that processes recent shipments in bulk.

Step 4 — Webhooks for Push Updates (4 min)

Polling every shipment every hour is wasteful and slow. Webhooks reverse the flow: WhereParcel calls you when a shipment’s status changes.

Register Your Webhook

In the dashboard, add an endpoint URL like https://yourapp.com/webhooks/whereparcel and pick the events you care about (status changes, delivered, exceptions).

Receive It

// Express example
app.post('/webhooks/whereparcel', express.json(), (req, res) => {
  const { trackingNumber, carrier, status, events } = req.body;
  // Persist the update
  await db.shipments.update({ trackingNumber }, { status, events });
  res.status(200).send('ok');
});

Two production essentials:

  1. Verify the signature — every webhook includes an X-WhereParcel-Signature header. Compute the HMAC-SHA256 of the raw body using your webhook secret and reject mismatches.
  2. Respond 2xx fast — do the heavy work in a queue. WhereParcel uses exponential-backoff retries on non-2xx responses, but slow handlers eat into your timeout budget.

Step 5 — Production Checklist (5 min) {#production-checklist}

A working request is not a working integration. Run through this before you ship:

  • API key in env vars only. Never in source, never in front-end bundles
  • Auto-detection in place so you don’t have a per-carrier if/else ladder
  • Idempotency keys on any code path that creates shipments
  • Webhook signature verification
  • Webhook handler queues work rather than blocking on heavy DB writes
  • Polling fallback for environments where you can’t receive webhooks
  • Rate-limit aware retries — Starter is 30 req/min, Pro is 60, Business is 200; back off on 429
  • Status enum mapping to your internal model (e.g. in_transitSHIPPED_IN_PROGRESS)
  • Error logging with the request ID that the API returns in headers
  • Sandbox vs production keys kept in separate environments

Common Issues and How to Fix Them

SymptomLikely causeFix
401 UnauthorizedKey missing the Bearer prefixAdd Authorization: Bearer YOUR_KEY
429 Too Many RequestsBurst exceeded plan rate limitAdd exponential backoff or upgrade plan
Status stuck on pendingCarrier hasn’t scanned the parcel yetPoll or re-check after several hours; not an API issue
carrier: "auto" returns wrong carrierTracking number shape collides between carriersPass the carrier explicitly when you know it
Webhook not firingWrong URL, dev server unreachable, or event filter excludes the changeCheck dashboard logs and signature verification

Next Steps

If you got stuck anywhere in the five minutes, ping support from the dashboard — we usually answer same-day.