Real-Time Shipment Tracking: Architecture and Implementation
Why tracking matters more than it seems
Shipment tracking started as a customer service feature: the customer wants to know where their package is. But it has evolved into something much more strategic. A well-implemented real-time tracking system doesn’t just answer “where is my shipment” but feeds operational optimization: anomaly detection in routes, arrival time estimation, triggering downstream processes (dock preparation, customs documentation, billing), and generating data for historical analysis.
The problem is that most tracking systems we encounter in mid-sized logistics companies aren’t truly real-time. They’re status systems that update when someone scans a package at a checkpoint: warehouse departure, hub arrival, departure to destination, delivery. That gives 4-6 data points per shipment. A real-time system with GPS gives a data point every 30-60 seconds, which fundamentally changes visibility and optimization possibilities.
Building a real-time tracking system isn’t trivial. Data volume, source heterogeneity, and the need for low latency create serious architectural challenges. This article describes the architecture we’ve implemented and validated.
General architecture
The system has five layers:
Sources → Ingestion → Processing → Storage → Presentation
Sources. Vehicle GPS devices (OBD-II trackers or dedicated devices like Teltonika FMB920), driver mobile apps (Android/iOS with native GPS), scanners at checkpoints (warehouses, hubs), external carrier APIs (DHL, FedEx, UPS provide webhooks or tracking APIs), and IoT sensors in containers (temperature, humidity, door opening).
Ingestion. Layer that receives data from all sources, normalizes the format, and publishes to an event bus. This is the critical point: if ingestion fails, the entire system goes blind.
Processing. Real-time business logic: geofencing (detecting when a vehicle enters/exits a zone), ETA calculation, anomaly detection (unplanned stops, route deviation, anomalous speed), and event correlation (associating the GPS position with the correct shipment).
Storage. Dual layer: hot storage for current state (where each shipment is right now) and cold storage for history (all positions for all shipments).
Presentation. Operational dashboards, public tracking page for the customer, automatic notifications, and APIs for integration with other systems (TMS, ERP, e-commerce platforms).
Event sourcing: the key architectural decision
The most important architectural decision is treating every position update as an immutable event instead of directly updating the shipment’s “current state.” Event sourcing means you store all events (position_reported, scan_performed, geofence_triggered, anomaly_detected) in an ordered sequence, and the current state is derived from that sequence.
Why this matters:
- Complete audit trail. You can reconstruct exactly what happened with a shipment at any point. When a client claims their package arrived late, you can show the complete route with timestamps.
- Reprocessing. If you discover an error in the ETA calculation logic, you can reprocess historical events with corrected logic without losing data.
- Multiple views. The same event stream feeds the operational dashboard, client notifications, historical analysis, and billing. Each consumer reads the events it needs without affecting the others.
Implementation: Apache Kafka as the event bus. Each event type has its topic (gps_positions, scan_events, geofence_events). Producers publish events; consumers process them.
Kafka is the right choice for this scale. For smaller operations (< 50 vehicles), Amazon SQS or Redis Streams may be sufficient and significantly simpler to operate.
GPS ingestion: the first bottleneck
A GPS device reporting every 30 seconds generates 2,880 points per day. With a fleet of 200 vehicles, that’s 576,000 daily points, or 6.7 points per second. With 1,000 vehicles, 33 points per second. It’s not big data, but latency matters: each point must be available for processing in less than 2 seconds.
Communication protocol
GPS trackers send data via TCP/UDP using proprietary protocols. The most common:
- Codec 8 Extended (Teltonika): efficient binary protocol. A packet with position, speed, altitude, and 10 additional parameters occupies 80-100 bytes.
- MQTT: used by mobile apps and some modern trackers. More flexible, slightly higher overhead.
- HTTP/REST: the simplest for app integration. Higher overhead, but easier to debug and maintain.
The ingestion layer consists of a server that speaks the tracker protocols, decodes the packets, normalizes the format to a common schema, and publishes to Kafka.
Implementation: a Python service with asyncio to handle multiple concurrent TCP connections. For Teltonika trackers, we use an open-source decoder (pyteltonica or similar). For MQTT, Mosquitto as broker connected to a consumer that republishes to Kafka. For HTTP, a FastAPI endpoint.
Real throughput
In our production deployment for a logistics company with 350 vehicles:
| Metric | Value |
|---|---|
| GPS points/second (peak) | 18 |
| GPS points/day | 980,000 |
| Ingestion latency (p95) | 380ms |
| End-to-end latency (GPS → dashboard) | 1.8s |
| Ingestion service availability | 99.94% (last quarter) |
The bottleneck isn’t processing but device connectivity. In rural areas or tunnels, trackers store positions in buffer and send them in bursts when they regain connectivity. The system must handle these bursts without degrading latency for normal traffic.
Geofencing: the value multiplier
Geofencing (detecting entry/exit from predefined geographic zones) transforms position data into business events. A GPS point saying “latitude 37.3861, longitude -5.9925” means nothing to an operator. An event saying “vehicle AB-1234-CD has entered the Port of Seville unloading zone at 14:23” is actionable.
Implementation
Geofences are defined as polygons (GeoJSON). Critical points for a logistics company: warehouses, ports, cargo terminals, frequent client addresses, traffic restriction zones, borders.
For each GPS position, the system evaluates whether the point is inside any active geofence. The classic algorithm is ray casting (O(n) where n is the number of polygon vertices), but with hundreds of geofences you need a spatial index.
We use PostGIS (PostgreSQL spatial extension) with its ST_Contains operator. PostGIS uses an R-tree index that allows evaluating thousands of geofences in milliseconds. The query:
SELECT g.id, g.name, g.type
FROM geofences g
WHERE ST_Contains(g.polygon, ST_SetSRID(ST_MakePoint(-5.9925, 37.3861), 4326));
For high-frequency flows (> 100 positions/second), we move evaluation to an in-memory service with Python’s shapely library and a pre-loaded spatial index. This avoids PostgreSQL query latency and allows evaluating geofences in < 1ms.
Geofence events
When a vehicle enters or exits a geofence, an event is generated. Common types:
- ENTER_WAREHOUSE: triggers load/unload preparation.
- EXIT_WAREHOUSE: confirms shipment departure, updates next point ETA.
- ENTER_PORT: notifies customs agent, prepares documentation.
- ENTER_DELIVERY_ZONE: notifies recipient with precise ETA.
- EXIT_ROUTE: route anomaly, alerts operator.
These events feed downstream automations that eliminate hours of daily manual work.
ETA calculation: the Holy Grail of tracking
Customers don’t want to know where their shipment is. They want to know when it arrives. ETA (Estimated Time of Arrival) is the most valuable metric and the most difficult to calculate accurately.
Basic model
Remaining distance divided by average speed. Works for simple routes and short distances. Accuracy: +/- 30-60 minutes.
Historical data model
Train a model with previous delivery data on the same route: departure time, day of week, weather conditions, actual duration. Random Forest or Gradient Boosting with these features gives accuracy of +/- 15-25 minutes.
Real-time data model
Incorporate real-time traffic (Google Maps Platform or HERE API), current vehicle position, and remaining stops. Recalculate every 5 minutes. Accuracy: +/- 8-15 minutes.
In practice, the hybrid model (historical + real-time) works best. Historical provides the baseline; real-time continuously corrects it. The cost of traffic APIs (Google Maps charges $5 per 1,000 Directions API requests) is the limiting factor for frequent updates. We use aggressive caching: if a vehicle is on the same road as 5 minutes ago, we don’t recalculate the route.
Notifications: the last mile
A tracking system without proactive notifications forces the customer to check manually. Automatic notifications transform tracking from pull (customer asks) to push (system informs).
Events that trigger notifications:
| Event | Recipient | Channel |
|---|---|---|
| Shipment picked up | Sender | |
| In transit | Recipient | Email + SMS |
| Out for delivery (< 2 hours) | Recipient | SMS + Push |
| Delivered | Sender + Recipient | |
| Incident (delay, failed attempt) | Sender | Email + SMS |
The channel matters. An “your shipment is in transit” email is informational. An SMS saying “your shipment will arrive between 14:00 and 15:00” when the customer is waiting for a package is operational. The difference in customer satisfaction between the two is significant.
Implementation: geofence events and ETA recalculations feed a notification service that decides what to send, to whom, and through which channel. We use templates by language and event type. Sending is done via Brevo for email, Twilio for SMS, and Firebase for push notifications.
Operational dashboard: the controller’s view
The dashboard is where the operations team sees the status of all shipments in real time. What we display:
Real-time map. Position of all active vehicles with color coding: green (on route, no anomalies), yellow (minor delay), red (anomaly: stopped > 30 min, off route, no signal > 1 hour).
Alert panel. Active anomalies ordered by severity. Each alert has an “acknowledge” button for the operator to confirm awareness and a recommended action.
Shipment table. List of all active shipments with status, ETA, last event, and carrier. Filterable by route, client, priority, and status.
Operational KPIs. Punctuality (% of shipments delivered within ETA), average delivery time by route, number of anomalies per day, incident resolution time.
Technology: Grafana for operational metrics panels. For the real-time map, a React component with Mapbox GL JS consuming positions via WebSocket (update every 5 seconds). The data source for the map is Redis, which stores the last position of each vehicle as a hash.
Storage: hot and cold
Hot storage (current state). Redis with the structure:
vehicle:{id}:position→ last known position (lat, lng, timestamp, speed, heading).shipment:{id}:status→ current shipment status (in_transit, out_for_delivery, delivered).shipment:{id}:eta→ current ETA.
Redis allows reads in < 1ms, which is what the dashboard needs to update 500 vehicles simultaneously.
Cold storage (historical). TimescaleDB (PostgreSQL with time series extension) for all GPS positions. With compression enabled, 1 million GPS points (each with 15 fields) occupy approximately 80 MB. For a fleet of 500 vehicles over one year, total storage is about 500 GB, manageable on a standard database instance.
Typical historical queries: “show the complete route for shipment X,” “calculate the average transit time Madrid-Barcelona in the last 6 months,” “identify unplanned stops longer than 20 minutes on route Y.”
TimescaleDB with its hypertables and temporal indices resolves these queries in seconds even over months of data. For larger scales (> 5,000 vehicles), ClickHouse or Apache Druid offer better performance on analytical queries over massive volumes.
External carrier integration
Not all shipments travel in owned vehicles. When a shipment is handed to an external carrier (DHL, SEUR, FedEx, contracted transport), you need to integrate their tracking into your system to offer a unified view to the client.
Carriers expose tracking in two ways:
- Webhooks. The carrier sends you events when shipment status changes. The preferred option: low latency, no polling. DHL Express, FedEx, and UPS offer webhooks.
- Polling API. Your system periodically queries the carrier’s API for current status. Necessary when the carrier doesn’t offer webhooks. Polling frequency balances data freshness with API rate limits (typically 1 query every 5-15 minutes per shipment).
The challenge: each carrier has its own status taxonomy. “In transit” at DHL doesn’t mean exactly the same as at FedEx. The normalization layer maps each carrier’s statuses to your unified internal taxonomy.
End-to-end performance
Real benchmarks from our production deployment:
| Metric | Target | Actual |
|---|---|---|
| Latency GPS → Kafka | < 500ms | 380ms (p95) |
| Latency Kafka → geofence event | < 200ms | 120ms (p95) |
| Latency geofence → notification | < 5s | 3.2s (p95) |
| Latency GPS → dashboard | < 3s | 1.8s (p95) |
| Ingestion throughput | > 50 msg/s | 85 msg/s (max tested) |
| Availability | > 99.9% | 99.94% |
The system runs on Railway with 3 main services (ingestion, processing, API), a managed Kafka instance (Confluent Cloud), managed Redis, and TimescaleDB. The total monthly cost for 350 vehicles and ~1M GPS points/day is approximately 450 EUR, including hosting, Kafka, and traffic APIs.
For larger fleets, the architecture scales horizontally: more Kafka partitions, more processing service instances, and TimescaleDB sharding. We haven’t tested beyond 2,000 simulated vehicles, where ingestion throughput reaches 180 msg/s without degradation.
Real-time tracking is not a luxury. It’s the infrastructure on which intelligent logistics operations are built. To understand how these tracking events feed broader automation flows, see our article on event-driven architecture for logistics. Without position data, everything else (route optimization, precise ETAs, anomaly detection, process automation) is impossible.
About the author
abemon engineering
Engineering team
Multidisciplinary engineering, data and AI team headquartered in the Canary Islands. We build, deploy and operate custom software solutions for companies at any scale.
