
Webhook Implementation for Real-Time Shipping Updates
Build reliable webhook handlers for shipping notifications. Event types, signature verification, and best practices for tracking updates.

Webhooks in Shipping
Webhooks enable real-time shipping updates without constant API polling. Instead of asking "what's the status?" every minute, carriers push updates to your endpoint when events occur.
Webhook Architecture
Event Flow
Carrier System → Webhook Event → Your Endpoint → Database Update → Customer Notification
Benefits Over Polling
| Aspect | Polling | Webhooks |
|---|---|---|
| Latency | Minutes | Seconds |
| API calls | Thousands/day | Only when needed |
| Cost | Higher | Lower |
| Resource usage | Continuous | Event-driven |
| Reliability | Depends on poll frequency | Real-time |
Event Types
Shipping Lifecycle Events
| Event | Trigger | Use Case |
|---|---|---|
| shipment.created | Label generated | Confirm order shipped |
| tracker.created | Tracking initiated | Enable tracking page |
| tracker.updated | Status change | Update order status |
| tracker.delivered | Package delivered | Close order |
| tracker.exception | Delivery issue | Alert support |
Status Update Events
| Status | Description |
|---|---|
| pre_transit | Label created, not yet scanned |
| in_transit | Package moving |
| out_for_delivery | With delivery driver |
| delivered | Successfully delivered |
| return_to_sender | Being returned |
| failure | Delivery failed |
Webhook Endpoint Implementation
Basic Handler
const express = require('express');
const app = express();app.post('/webhooks/shipping', async (req, res) => {
// Acknowledge immediately
res.status(200).send('OK');
// Process asynchronously
setImmediate(async () => {
try {
await processWebhook(req.body);
} catch (error) {
console.error('Webhook processing error:', error);
}
});
});
Event Processing
const processWebhook = async (event) => {
switch (event.type) {
case 'tracker.updated':
await handleTrackingUpdate(event.data);
break;
case 'tracker.delivered':
await handleDelivery(event.data);
break;
case 'tracker.exception':
await handleException(event.data);
break;
default:
console.log('Unknown event type:', event.type);
}
};const handleTrackingUpdate = async (data) => {
// Find order by tracking number
const order = await findOrderByTracking(data.tracking_code);
if (!order) return;
// Update order status
await updateOrderStatus(order.id, mapStatus(data.status));
// Store tracking event
await createTrackingEvent(order.id, {
status: data.status,
location: data.location,
timestamp: data.datetime,
description: data.message
});
// Notify customer if significant
if (shouldNotifyCustomer(data.status)) {
await sendTrackingNotification(order, data);
}
};
Signature Verification
HMAC Verification
const crypto = require('crypto');const verifyWebhookSignature = (req) => {
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
};
app.post('/webhooks/shipping', (req, res) => {
if (!verifyWebhookSignature(req)) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
});
Timestamp Validation
const validateTimestamp = (timestamp) => {
const eventTime = new Date(timestamp);
const now = new Date();
const fiveMinutes = 5 60 1000; // Reject events older than 5 minutes
return (now - eventTime) < fiveMinutes;
};
Idempotency
Preventing Duplicate Processing
const processWebhook = async (event) => {
// Check if already processed
const processed = await redis.get(webhook:${event.id}); if (processed) {
console.log('Webhook already processed:', event.id);
return;
}
// Mark as processing
await redis.setex(webhook:${event.id}, 86400, 'processing');
try {
await handleEvent(event);
await redis.setex(webhook:${event.id}, 86400, 'completed');
} catch (error) {
await redis.del(webhook:${event.id});
throw error;
}
};
Event ID Storage
| Storage | TTL | Use Case |
|---|---|---|
| Redis | 24 hours | High-volume |
| Database | 7 days | Audit trail |
| Memory | Request lifecycle | Simple apps |
Retry and Error Handling
Webhook Retry Policies
| Attempt | Delay | Max Retries |
|---|---|---|
| 1 | Immediate | - |
| 2 | 5 seconds | - |
| 3 | 30 seconds | - |
| 4 | 2 minutes | - |
| 5 | 10 minutes | - |
| 6+ | 1 hour | 24 hours total |
Error Response Codes
| Code | Meaning | Retry |
|---|---|---|
| 200 | Success | No |
| 400 | Bad request | No |
| 401 | Auth failed | No |
| 500 | Server error | Yes |
| 502 | Gateway error | Yes |
| 503 | Service unavailable | Yes |
Dead Letter Queue
const handleFailedWebhook = async (event, error) => {
// Store in dead letter queue
await deadLetterQueue.add({
event,
error: error.message,
timestamp: new Date(),
retryCount: event.retryCount || 0
}); // Alert if critical
if (isCriticalEvent(event.type)) {
await alertOps({
type: 'webhook_failure',
event: event.type,
error: error.message
});
}
};
Customer Notification Integration
Notification Rules
const notificationRules = {
'in_transit': {
send: true,
channel: 'email',
template: 'shipment_in_transit'
},
'out_for_delivery': {
send: true,
channel: 'sms',
template: 'out_for_delivery'
},
'delivered': {
send: true,
channel: 'email',
template: 'delivery_confirmation'
},
'exception': {
send: true,
channel: 'both',
template: 'delivery_exception'
}
};const notifyCustomer = async (order, trackingEvent) => {
const rule = notificationRules[trackingEvent.status];
if (!rule?.send) return;
if (rule.channel === 'email' || rule.channel === 'both') {
await sendEmail(order.customer.email, rule.template, {
order,
tracking: trackingEvent
});
}
if (rule.channel === 'sms' || rule.channel === 'both') {
await sendSMS(order.customer.phone, rule.template, {
order,
tracking: trackingEvent
});
}
};
Monitoring and Debugging
Webhook Logging
const logWebhook = async (req, result) => {
await webhookLogs.create({
eventId: req.body.id,
eventType: req.body.type,
payload: req.body,
headers: req.headers,
result: result,
timestamp: new Date(),
processingTime: result.duration
});
};
Dashboard Metrics
| Metric | Description | Alert Threshold |
|---|---|---|
| Success rate | % processed successfully | < 99% |
| Latency | Processing time | > 1 second |
| Queue depth | Pending webhooks | > 100 |
| Error rate | % failed | > 1% |
Health Check Endpoint
app.get('/webhooks/health', async (req, res) => {
const status = {
healthy: true,
lastEvent: await getLastEventTime(),
queueDepth: await getQueueDepth(),
errorRate: await getErrorRate(),
uptime: process.uptime()
}; const statusCode = status.healthy ? 200 : 503;
res.status(statusCode).json(status);
});
Testing Webhooks
Local Development
| Tool | Purpose |
|---|---|
| ngrok | Expose local endpoint |
| localtunnel | Alternative to ngrok |
| webhook.site | Inspect payloads |
| Postman | Manual testing |
Test Event Generation
const testEvents = {
delivered: {
type: 'tracker.delivered',
data: {
tracking_code: 'TEST123',
status: 'delivered',
datetime: new Date().toISOString(),
location: { city: 'New York', state: 'NY' }
}
}
};// Trigger test event
await fetch('http://localhost:3000/webhooks/shipping', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(testEvents.delivered)
});
Atoship Webhook Features
| Feature | Capability |
|---|---|
| Event types | All shipping lifecycle events |
| Retry policy | Automatic with backoff |
| Signature verification | HMAC-SHA256 |
| Event logs | 30-day retention |
| Testing tools | Sandbox webhooks |
Set Up Shipping Webhooks
Create your Atoship account for real-time shipping updates via webhooks.
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



