
Shipping API Integration: Developer Guide for E-commerce Platforms
Technical guide to integrating shipping APIs into your e-commerce platform. Authentication, rate shopping, label generation, and webhook handling.

Shipping API Integration Overview
Integrating shipping APIs enables automated rate shopping, label generation, and tracking. This guide covers best practices for developers building shipping functionality.
API Architecture Patterns
Direct Carrier Integration
| Carrier | API Style | Auth Method |
|---|---|---|
| USPS | REST/XML | User ID |
| UPS | REST | OAuth 2.0 |
| FedEx | REST | OAuth 2.0 |
| DHL | REST | API Key |
Aggregator Integration (Recommended)
| Provider | Carriers | Single API |
|---|---|---|
| Atoship | USPS, UPS, FedEx, DHL | Yes |
| EasyPost | Multiple | Yes |
| ShipEngine | Multiple | Yes |
Authentication Implementation
OAuth 2.0 Flow (UPS/FedEx)
// OAuth token request
const getToken = async () => {
const response = await fetch('https://api.carrier.com/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa(clientId + ':' + clientSecret)
},
body: 'grant_type=client_credentials'
});
return response.json();
};
API Key Authentication
// API key in headers
const headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
};
Token Refresh Strategy
| Strategy | Implementation |
|---|---|
| Pre-emptive refresh | Refresh 5 min before expiry |
| On-error refresh | Refresh on 401 response |
| Sliding window | Refresh on each request |
Rate Shopping Implementation
Request Structure
{
"from_address": {
"name": "Sender",
"street1": "123 Main St",
"city": "New York",
"state": "NY",
"zip": "10001",
"country": "US"
},
"to_address": {
"name": "Recipient",
"street1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US"
},
"parcel": {
"length": 10,
"width": 8,
"height": 4,
"weight": 32
}
}
Response Handling
// Parse rate response
const selectBestRate = (rates) => {
// Sort by price
const sorted = rates.sort((a, b) => a.rate - b.rate); // Filter by service level
const groundRates = sorted.filter(r =>
r.service.includes('ground') || r.service.includes('standard')
);
return groundRates[0] || sorted[0];
};
Label Generation
Create Shipment Request
{
"shipment": {
"from_address": { / address object / },
"to_address": { / address object / },
"parcel": { / parcel object / },
"carrier": "usps",
"service": "ground_advantage"
},
"options": {
"label_format": "PDF",
"label_size": "4x6"
}
}
Response Processing
// Handle label response
const processLabel = async (shipmentId) => {
const response = await createShipment(shipmentData); return {
trackingNumber: response.tracking_code,
labelUrl: response.label_url,
labelBase64: response.label_base64,
rate: response.rate,
carrier: response.carrier
};
};
Batch Label Generation
// Process multiple labels
const batchCreateLabels = async (orders) => {
const promises = orders.map(order =>
createShipment(order).catch(err => ({ error: err, orderId: order.id }))
); const results = await Promise.allSettled(promises);
return {
successful: results.filter(r => r.status === 'fulfilled'),
failed: results.filter(r => r.status === 'rejected')
};
};
Webhook Integration
Tracking Webhooks
// Webhook endpoint
app.post('/webhooks/tracking', async (req, res) => {
const event = req.body; // Verify webhook signature
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// Process tracking update
switch (event.type) {
case 'tracker.updated':
await updateOrderTracking(event.data);
break;
case 'tracker.delivered':
await markOrderDelivered(event.data);
break;
}
res.status(200).send('OK');
});
Webhook Event Types
| Event | Description |
|---|---|
| tracker.created | Tracking initiated |
| tracker.updated | Status changed |
| tracker.delivered | Package delivered |
| tracker.exception | Delivery issue |
Error Handling
Common Error Codes
| Code | Meaning | Action |
|---|---|---|
| 400 | Bad request | Validate input |
| 401 | Unauthorized | Refresh token |
| 404 | Not found | Check resource ID |
| 429 | Rate limited | Implement backoff |
| 500 | Server error | Retry with backoff |
Retry Strategy
const retryWithBackoff = async (fn, maxRetries = 3) => {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (err) {
if (i === maxRetries - 1) throw err;
if (err.status === 429 || err.status >= 500) {
await sleep(Math.pow(2, i) * 1000);
} else {
throw err;
}
}
}
};
Address Validation
Validation Request
{
"address": {
"street1": "123 Main Street",
"city": "New York",
"state": "NY",
"zip": "10001",
"country": "US"
}
}
Validation Response Handling
const validateAndCorrect = async (address) => {
const result = await validateAddress(address); if (result.verifications.delivery.success) {
return {
valid: true,
corrected: result.address,
changes: result.verifications.delivery.details
};
}
return {
valid: false,
errors: result.verifications.delivery.errors
};
};
Rate Caching Strategy
Cache Implementation
const getRates = async (shipment) => {
const cacheKey = generateCacheKey(shipment); // Check cache
const cached = await cache.get(cacheKey);
if (cached && !isExpired(cached)) {
return cached.rates;
}
// Fetch fresh rates
const rates = await fetchRates(shipment);
// Cache for 15 minutes
await cache.set(cacheKey, { rates, timestamp: Date.now() }, 900);
return rates;
};
Cache Key Generation
const generateCacheKey = (shipment) => {
const { from, to, parcel } = shipment;
return rates:${from.zip}:${to.zip}:${parcel.weight}:${parcel.length}x${parcel.width}x${parcel.height};
};
Testing and Sandbox
Sandbox Environments
| Provider | Sandbox URL | Test Data |
|---|---|---|
| UPS | wwwcie.ups.com | Test credentials |
| FedEx | wsbeta.fedex.com | Test credentials |
| USPS | stg-production.shippingapis.com | Test user ID |
Integration Testing
describe('Shipping API', () => {
it('should return rates for valid shipment', async () => {
const rates = await getRates(testShipment);
expect(rates.length).toBeGreaterThan(0);
expect(rates[0].rate).toBeGreaterThan(0);
}); it('should create label successfully', async () => {
const label = await createLabel(testShipment);
expect(label.trackingNumber).toBeDefined();
expect(label.labelUrl).toBeDefined();
});
});
Atoship API Advantages
| Feature | Benefit |
|---|---|
| Single integration | All carriers via one API |
| Pre-negotiated rates | Commercial pricing included |
| Unified response format | Consistent data structure |
| Sandbox environment | Test before production |
| Webhook support | Real-time tracking updates |
Get Started with Atoship API
Sign up for Atoship and access our developer-friendly shipping API with comprehensive documentation.
Ready to save on shipping?
Get started with Atoship for free and access discounted USPS, UPS, and FedEx rates. No monthly fees, no contracts.
Create Free Account



