Parcel Tracking Security: Protecting Shipment Data and API Keys

Shipping data is more sensitive than most developers realize. A tracking number can reveal purchase history, home addresses, delivery patterns, and business relationships. As you build tracking integrations, security should be a first-class concern — not an afterthought. This guide covers the essential security practices for any application that handles parcel tracking data.

Securing Your API Keys

Never Expose Keys in Client-Side Code

The most common mistake: embedding API keys in frontend JavaScript.

// WRONG — API key visible to anyone who opens browser DevTools
const response = await fetch('https://api.whereparcel.com/v2/track', {
  headers: { 'Authorization': 'Bearer wp_live_abc123...:sk_live_xyz789...' },
});

Instead, proxy requests through your backend:

// Frontend — calls your own API
const response = await fetch('/api/tracking', {
  method: 'POST',
  body: JSON.stringify({ trackingNumber: '123456789012' }),
});

// Backend — adds API key server-side
app.post('/api/tracking', async (req, res) => {
  const result = await fetch('https://api.whereparcel.com/v2/track', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.WHEREPARCEL_API_KEY}:${process.env.WHEREPARCEL_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      trackingItems: [{ carrier: req.body.carrier, trackingNumber: req.body.trackingNumber }],
    }),
  });

  res.json(await result.json());
});

Use Environment Variables

Store API keys in environment variables, never in code:

# .env (never commit this file)
WHEREPARCEL_API_KEY=wp_live_abc123...

# .gitignore
.env
.env.local
.env.production

Rotate Keys Regularly

Set a schedule for key rotation and use the WhereParcel dashboard to manage multiple active keys:

  1. Generate a new API key
  2. Deploy the new key to your application
  3. Verify the new key works in production
  4. Revoke the old key

Preventing Tracking Number Enumeration

Tracking numbers are often sequential or follow predictable patterns. Without proper protection, attackers can enumerate tracking numbers to access other customers’ shipment data.

Rate Limit Your Tracking Endpoint

import rateLimit from 'express-rate-limit';

const trackingLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 30, // 30 requests per window per IP
  message: { error: 'Too many tracking requests, please try again later' },
  standardHeaders: true,
});

app.use('/api/tracking', trackingLimiter);

Require Authentication

Don’t allow anonymous tracking lookups if you can avoid it:

app.post('/api/tracking', authenticate, async (req, res) => {
  const { trackingNumber } = req.body;

  // Verify this tracking number belongs to the authenticated user
  const shipment = await db.shipments.findOne({
    trackingNumber,
    userId: req.user.id,
  });

  if (!shipment) {
    return res.status(404).json({ error: 'Shipment not found' });
  }

  // Only then fetch tracking data
  const tracking = await getTracking(trackingNumber, shipment.carrier);
  res.json(tracking);
});

Add Request Fingerprinting

Detect and block enumeration attempts:

async function detectEnumeration(req) {
  const key = `tracking:${req.ip}`;
  const recentNumbers = await redis.lrange(key, 0, -1);

  // Track which numbers this IP has queried
  await redis.lpush(key, req.body.trackingNumber);
  await redis.ltrim(key, 0, 99); // Keep last 100
  await redis.expire(key, 3600); // 1 hour window

  // Flag if querying many different tracking numbers
  const uniqueNumbers = new Set(recentNumbers).size;
  if (uniqueNumbers > 20) {
    await flagSuspiciousActivity(req.ip, 'tracking_enumeration');
    return true;
  }

  return false;
}

Protecting Sensitive Data

Minimize Data Storage

Only store what you need:

// Store minimal tracking data
const trackingRecord = {
  trackingNumber: shipment.trackingNumber,
  carrier: shipment.carrier,
  currentStatus: tracking.status,
  lastUpdated: tracking.lastEvent.timestamp,
  // DON'T store: full address, recipient name, package contents
};

Mask Tracking Numbers in Logs

Never log full tracking numbers in application logs:

function maskTrackingNumber(number) {
  if (number.length <= 6) return '***';
  return number.slice(0, 3) + '*'.repeat(number.length - 6) + number.slice(-3);
}

// Log: "Tracking lookup for 123******890"
logger.info(`Tracking lookup for ${maskTrackingNumber(trackingNumber)}`);

Encrypt Data at Rest

If you cache tracking data, encrypt sensitive fields:

import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

function encryptField(text, key) {
  const iv = randomBytes(16);
  const cipher = createCipheriv('aes-256-gcm', key, iv);
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  const authTag = cipher.getAuthTag();
  return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}

Webhook Security

When receiving tracking updates via webhooks, verify the source:

Validate Webhook Signatures

import crypto from 'crypto';

app.post('/webhooks/tracking', (req, res) => {
  const signature = req.headers['x-whereparcel-signature'];
  const payload = JSON.stringify(req.body);

  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process webhook...
  res.status(200).json({ received: true });
});

Use HTTPS Only

Always use HTTPS for webhook endpoints. Reject HTTP webhook registrations.

Verify Webhook Source IPs

Optionally restrict webhook processing to WhereParcel’s IP ranges:

const ALLOWED_IPS = [
  // WhereParcel webhook IPs — check docs for current list
  '203.0.113.0/24',
];

app.post('/webhooks/tracking', verifySourceIP(ALLOWED_IPS), processWebhook);

Data Privacy Compliance

GDPR Considerations

If you serve European customers:

  • Data minimization — Only collect tracking data you need
  • Right to erasure — Be able to delete a customer’s tracking history
  • Purpose limitation — Don’t use tracking data for purposes beyond delivery tracking
  • Data retention — Set automatic deletion policies for old tracking data
// Automatic data cleanup
async function cleanupOldTrackingData() {
  const retentionPeriod = 90; // days
  const cutoff = new Date();
  cutoff.setDate(cutoff.getDate() - retentionPeriod);

  await db.trackingEvents.deleteMany({
    createdAt: { $lt: cutoff },
  });
}

Cross-Border Data Considerations

Tracking data crosses borders by nature. Be aware of:

  • Korea’s PIPA — Personal Information Protection Act applies to Korean shipment data
  • Japan’s APPI — Act on Protection of Personal Information
  • China’s PIPL — Personal Information Protection Law

WhereParcel processes data in compliance with these regulations, but your application must also handle the data responsibly.

Security Checklist

Use this checklist for your tracking integration:

  • API keys stored in environment variables, not code
  • API calls proxied through backend, not made from client
  • Tracking endpoint rate-limited
  • Authentication required for tracking lookups
  • Tracking numbers not logged in full
  • Webhook signatures validated
  • HTTPS enforced for all endpoints
  • Data retention policy implemented
  • Old tracking data automatically purged
  • API key rotation schedule defined
  • Enumeration detection in place

Summary

Security in shipping integrations comes down to a few core principles: protect your API keys, control access to tracking data, verify webhook sources, and respect data privacy regulations. These practices protect both your business and your customers’ sensitive shipment information.

For API authentication details, see our Getting Started guide. For webhook security, see Webhook Best Practices.