webhooksreal-time trackingAPI integration

Webhook Implementation for Real-Time Shipping Updates

Build reliable webhook handlers for shipping notifications. Event types, signature verification, and best practices for tracking updates.

January 9, 20266 min read60 views
Webhook Implementation for Real-Time Shipping 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

AspectPollingWebhooks
LatencyMinutesSeconds
API callsThousands/dayOnly when needed
CostHigherLower
Resource usageContinuousEvent-driven
ReliabilityDepends on poll frequencyReal-time

Event Types

Shipping Lifecycle Events

EventTriggerUse Case
shipment.createdLabel generatedConfirm order shipped
tracker.createdTracking initiatedEnable tracking page
tracker.updatedStatus changeUpdate order status
tracker.deliveredPackage deliveredClose order
tracker.exceptionDelivery issueAlert support

Status Update Events

StatusDescription
pre_transitLabel created, not yet scanned
in_transitPackage moving
out_for_deliveryWith delivery driver
deliveredSuccessfully delivered
return_to_senderBeing returned
failureDelivery 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

StorageTTLUse Case
Redis24 hoursHigh-volume
Database7 daysAudit trail
MemoryRequest lifecycleSimple apps

Retry and Error Handling

Webhook Retry Policies

AttemptDelayMax Retries
1Immediate-
25 seconds-
330 seconds-
42 minutes-
510 minutes-
6+1 hour24 hours total

Error Response Codes

CodeMeaningRetry
200SuccessNo
400Bad requestNo
401Auth failedNo
500Server errorYes
502Gateway errorYes
503Service unavailableYes

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

MetricDescriptionAlert Threshold
Success rate% processed successfully< 99%
LatencyProcessing time> 1 second
Queue depthPending 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

ToolPurpose
ngrokExpose local endpoint
localtunnelAlternative to ngrok
webhook.siteInspect payloads
PostmanManual 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

FeatureCapability
Event typesAll shipping lifecycle events
Retry policyAutomatic with backoff
Signature verificationHMAC-SHA256
Event logs30-day retention
Testing toolsSandbox webhooks

Set Up Shipping Webhooks

Create your Atoship account for real-time shipping updates via webhooks.

Share this article:

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