Architecture Azure

Logistics Track-and-Trace Platform on Azure IoT and Event Grid

A national parcel carrier — the kind that moves nine million packages a day across a hub-and-spoke network of sortation centres, line-haul trucks, and last-mile vans — gets a board mandate after a brutal peak season. Two numbers drove it: their “where is my parcel” page was a day behind reality, so customers called the contact centre instead, and each of those calls cost more than the margin on the shipment itself; and their largest retail client, a marketplace shipping a third of the carrier’s volume, threatened to move accounts unless the carrier could prove delivery ETAs to the hour and flag at-risk shipments before they breached SLA, not after. The old tracking system was a nightly batch that scraped scanner events from the warehouse management system into a data warehouse. By the time a dashboard refreshed, a truck that broke down at 9 a.m. was still showing “in transit, on time” at 4 p.m. The ask from the board is blunt: live location and a trustworthy ETA for every parcel, continuously, at fleet scale. This article is the reference architecture for building that on Azure — an IoT telemetry pipeline that ingests from tens of thousands of devices, routes events without a polling loop, computes ETAs in motion, and lands a geo-state a dashboard and an API can both trust.

The pressures are the ones every logistics platform hits at once. Volume means hundreds of thousands of telemetry messages per minute at peak, from GPS trackers on trucks, handheld scanners at every sort, and increasingly BLE tags on high-value parcels. Freshness means the gap between a physical event and the dashboard reflecting it has to shrink from a day to seconds, because that gap is the whole problem. Reliability means a tracker in a dead-zone tunnel, or a sortation centre’s network blip, cannot lose events or corrupt a parcel’s state. And cost means you cannot pay streaming-analytics prices on every heartbeat from every idle device — the architecture has to be cheap at rest and elastic under peak. The pattern that satisfies all four is an event-driven IoT pipeline: devices push telemetry into a managed broker, a router fans each event to exactly the consumers that care, a stream processor enriches and computes in motion, and a geo-distributed store holds the current truth that everything else reads.

Why not the obvious shortcuts

The naive fixes each fail predictably, and someone on the project will propose all three.

Keep polling the WMS and just run it more often. Shrinking a nightly batch to every five minutes multiplies load on the system of record, still lags reality by minutes, and does nothing for the line-haul leg between sort centres where there are no scans at all — exactly where the broken-down truck hides. Have every device POST to a REST API behind the app. At nine million parcels and tens of thousands of devices that is a thundering herd of connections your app tier was never sized for, with no per-device identity, no offline buffering when a truck loses signal in a tunnel, and no back-pressure when a region floods. Push everything straight into the SQL warehouse. Telemetry is high-velocity, append-mostly, and geospatial; a relational warehouse chokes on the write rate, and you still have no mechanism to react to an event — only to query it later, which is the batch problem again.

An IoT-and-events architecture threads the needle. Azure IoT Hub gives every device a managed, authenticated, bidirectional connection with offline buffering, so a tracker in a tunnel queues messages and flushes them on reconnect. Event Grid turns each arriving telemetry message into an event that is pushed to subscribers — no consumer polls anything. Stream Analytics computes ETA and detects anomalies in the moving stream. And Cosmos DB holds the current geo-state of every parcel as a document the customer API and the dashboards read in milliseconds. Reaction becomes first-class: a geofence breach or an ETA slip fires, it is not discovered hours later in a report.

Architecture overview

Logistics Track-and-Trace Platform on Azure IoT and Event Grid — architecture

The platform runs three cooperating paths that share infrastructure but live on different cadences: a high-volume telemetry ingestion path from devices, an event-driven routing and processing path that enriches and computes, and a serving path that exposes state to customers, operations, and the enterprise. Keeping them distinct is the first step to operating this well.

The defining property of the topology is that nothing polls. A device pushes; IoT Hub raises an event; Event Grid fans it to the handlers that subscribed to that event type; each handler does one job. That push-based spine is what lets the system stay cheap when the fleet is idle overnight and absorb a tenfold surge at peak without a redesign.

Telemetry ingestion path, following the data flow:

  1. GPS trackers on trucks and vans, handheld scanners at sort centres, and BLE gateways for tagged parcels each hold a per-device identity in IoT Hub (X.509 certificate for the fixed gateways, symmetric keys provisioned through Device Provisioning Service for the handheld fleet). Devices speak MQTT and buffer locally when offline, so a tunnel or a dropped sort-centre uplink delays but never loses telemetry.
  2. IoT Hub authenticates each connection, applies per-device throttling, and accepts the telemetry — location fixes, scan events (arrived_at_sort, loaded_to_linehaul, out_for_delivery), temperature for cold-chain parcels, and device health heartbeats. Cloud-to-device commands (re-provision, request immediate fix) flow back over the same connection.
  3. IoT Hub’s built-in message routing splits the firehose by message type before anything downstream pays for it: location and scan events go to the hot path; raw heartbeats and full-fidelity telemetry tee off to Azure Data Lake Storage through Event Hubs Capture for the audit trail, replay, and ML training, never touching the expensive streaming tier.

Routing and processing path, event-driven:

  1. Azure Event Grid is the control-plane router. IoT Hub publishes device lifecycle and telemetry events to a Grid topic, and Grid pushes each event to the subscriber that registered for that type — with built-in retry, dead-lettering to Storage, and filtering so a handler only ever sees the events it asked for. A DeviceTelemetry event drives the stream; a DeviceConnectionStateChanged event drives a “tracker went dark” alert; a geofence event drives a customer notification. No handler runs a receive loop.
  2. Azure Stream Analytics is the moving-window brain. It reads the location/scan stream, joins each fix against reference data (the planned route, the parcel-to-shipment mapping, sort-centre coordinates), and computes a live ETA with a tumbling/hopping window — speed-over-ground from successive fixes, distance remaining along the planned route, and historical leg durations. The same job runs anomaly queries: a truck stopped longer than its dwell threshold, a parcel that missed its expected scan, a cold-chain reading out of band. Its outputs go three places — the geo-state store, a notifications topic, and the dashboard feed.
  3. Azure Functions handle the discrete reactions Grid pushes to them: format and send a customer push/SMS on out_for_delivery or a geofence “arriving” event, raise an operational alert, update a shipment aggregate. They are stateless, scale to zero, and each does one small thing.

Serving path:

  1. Azure Cosmos DB holds the current geo-state of every parcel and shipment as a document — last known position, status, computed ETA, leg history — partitioned for the read patterns that matter. The customer “track my parcel” API and the carrier’s operations console read Cosmos directly for single-digit-millisecond lookups at any volume.
  2. Azure Maps provides geofencing, route geometry, reverse-geocoding (“near the Birmingham depot”), and the map tiles the tracking page renders. Stream Analytics and Functions call Maps to evaluate whether a fix has crossed a depot or delivery-zone geofence.
  3. Power BI drives the operational and client-facing dashboards — network heatmaps, on-time performance by lane, at-risk-shipment queues — reading the curated feed (Cosmos change feed materialised for BI, plus the Data Lake for historical trend). The marketplace client gets an embedded, row-level-secured view of their shipments only.

Component breakdown

Component Service / tool Role in the platform Key configuration choices
Device identity & ingest Azure IoT Hub Authenticated bidirectional device connectivity, offline buffering, message routing MQTT; X.509 for gateways; DPS for handhelds; route by message type
Device onboarding IoT Hub Device Provisioning Service Zero-touch enrolment and re-provisioning at fleet scale Enrolment groups; attestation by cert/key; allocation policy
Event router Azure Event Grid Push events to subscribers, retry, dead-letter, filter by type Event filtering per handler; dead-letter to Storage; managed-identity delivery
Stream processing Azure Stream Analytics ETA computation, geofence + anomaly detection in moving windows Hopping windows; reference-data join; multiple outputs
Reactions Azure Functions Notifications, alerts, aggregate updates Event Grid trigger; consumption plan; idempotent handlers
Geo-state store Azure Cosmos DB Current parcel/shipment state for API + dashboards Partition by region/route; autoscale RU/s; change feed; TTL on stale state
Geospatial Azure Maps Geofencing, routing, reverse-geocode, map tiles Geofence service; route directions API; spatial queries
Cold/warm storage ADLS + Event Hubs Capture Audit trail, replay, ML training data Capture to Parquet; lifecycle tiering hot→cool→archive
Dashboards Power BI Ops + client dashboards, on-time and at-risk views DirectQuery/streaming dataset; row-level security per client
Identity / SSO Okta + Microsoft Entra ID Workforce SSO (Okta) federated to Entra for native Azure RBAC OIDC federation; group claims to APIM/Power BI; conditional access
API gateway Azure API Management Front door for the customer track API; throttle, auth, cache validate-jwt; rate-limit by client; cache hot lookups
Secrets HashiCorp Vault Device-cert signing material, third-party carrier/Maps tokens Entra auth method; dynamic leases; short-lived secrets
CSPM / data posture Wiz + Wiz Code Cloud posture, exposure, IaC scanning of the Terraform before deploy Agentless scan of IoT/Storage/Cosmos; Wiz Code gate in PR
Runtime security CrowdStrike Falcon Runtime protection on AKS/edge gateway compute feeding the SOC Sensor on node pool and on-prem gateway VMs
Observability Dynatrace / Datadog End-to-end tracing, ingestion lag and ETA-accuracy telemetry OneAgent/agent on compute; custom IoT lag + accuracy metrics
Edge gateway Azure IoT Edge (virtual appliances) Local filtering/aggregation at sort centres before WAN Edge modules; store-and-forward; offline scoring
Edge / CDN Akamai TLS, anycast, WAF for the public tracking page and API WAF on the track endpoint; origin shield to APIM
ITSM ServiceNow Operational incidents, device-fleet change management Auto-ticket on “fleet-wide tracker dark”; CMDB for devices
CI / IaC GitHub Actions + Argo CD + Terraform/Ansible Pipeline, GitOps deploy of edge modules, infra as code OIDC to Azure; Argo CD syncs IoT Edge deployments; Ansible configures gateway VMs
Enablement Moodle Driver/depot training on the scanner app and exception handling Courses tied to onboarding; completion gates device issue

A few of these choices deserve the why, because they are the ones teams get wrong.

Why Event Grid and not “Stream Analytics reads everything.” It is tempting to point one big Stream Analytics job at the whole IoT firehose and let it sort out what to do. Don’t — that couples every reaction to one expensive always-on job and forces it to pay for events it will only discard. Event Grid is the cheap, push-based switchboard: it filters by event type at the platform level, so the ETA job only ingests location/scan events, the “tracker dark” Function only wakes on connection-state events, and the notification Function only fires on geofence events. Each consumer scales and fails independently, and Grid’s dead-lettering means a transient handler outage parks events in Storage instead of dropping them.

Why Cosmos DB for geo-state, and how to partition it. The serving workload is “give me the current state of this parcel” and “give me every parcel in this region/route” at high read volume with low latency — a key-value and bounded-range pattern, not analytics. Cosmos delivers single-digit-millisecond reads at any scale and, critically, a change feed that materialises updates into the Power BI feed and the search index without a second query path. Partitioning is the decision that makes or breaks it: partition by a composite of region + active route so a parcel’s hot writes and the “everything on this lane” reads stay within a partition, and set a TTL so delivered parcels age out of the hot store into the Data Lake instead of inflating RU cost forever.

Why ETA in the stream, not in a nightly model. A useful ETA decays by the minute; computing it in a batch makes it wrong on arrival. Stream Analytics joins each live fix to the planned route and historical leg durations and emits a fresh ETA continuously, so the dashboard and the customer page reflect the truck that just stopped moving. The heavier predictive model (traffic, weather, depot congestion) trains offline on the Data Lake and is served as a reference scoring step — the stream stays light, the intelligence stays current.

Implementation guidance

Provision with Terraform, and treat device identity and routing as the first deliverables. Get the IoT Hub routing and Event Grid subscriptions wrong and you either pay streaming prices on heartbeats or silently drop events that no handler subscribed to.

  1. The IoT Hub, with message routing rules splitting telemetry by messageType — location/scan to the hot endpoint, heartbeats/raw to the Capture endpoint — so the cost split is enforced at ingestion.
  2. Device Provisioning Service with enrolment groups so tens of thousands of handhelds and gateways onboard zero-touch and can be re-provisioned without a truck roll.
  3. The Event Grid system topic on the IoT Hub plus the subscriptions, each with an event-type filter and a dead-letter destination in Storage.
  4. The Stream Analytics job with its reference-data inputs (route plan, parcel-shipment map) and three outputs (Cosmos, the notification Grid topic, the BI feed).
  5. Cosmos DB with autoscale RU/s, the chosen partition key, change feed enabled, and TTL on the delivered-parcel documents.
  6. API Management in front of the customer track API, with Akamai at the edge.

A minimal Terraform shape for the IoT Hub communicates the intent — split the firehose at the source:

resource "azurerm_iothub" "track" {
  name                = "iot-trackvin-prod-uksouth"
  resource_group_name = azurerm_resource_group.track.name
  location            = "uksouth"
  sku { name = "S2"  capacity = 4 }     # units sized to peak msgs/sec

  route {
    name           = "hot-location-scan"
    source         = "DeviceMessages"
    condition      = "messageType IN ('location','scan')"
    endpoint_names = ["eg-hot"]          # to Event Grid / Stream Analytics
    enabled        = true
  }
  route {
    name           = "cold-capture"
    source         = "DeviceMessages"
    condition      = "messageType IN ('heartbeat','raw')"
    endpoint_names = ["capture-adls"]    # to Data Lake, off the hot tier
    enabled        = true
  }
}

The pipeline that applies this runs in GitHub Actions, authenticating to Azure via OIDC federation so there is no stored service-principal secret to leak. Wiz Code scans the Terraform in the pull request and fails the build if it would expose a public endpoint or weaken an RBAC scope, and Argo CD owns the GitOps deployment of the IoT Edge modules to the sort-centre gateways so an edge logic change rolls out the same way a Kubernetes manifest does. Ansible handles the base configuration of the on-prem gateway virtual appliances that sit upstream of the edge modules.

Identity: kill the keys for humans, manage them tightly for devices. Two identity planes coexist. Devices authenticate to IoT Hub with X.509 certs (gateways) or DPS-issued keys (handhelds); the signing material and the certificate authority secrets live in HashiCorp Vault, leased dynamically so nothing long-lived sits on disk, and DPS handles rotation without a depot visit. Humans — depot operators, the operations console, the carrier’s analysts, and the marketplace client’s staff viewing their embedded dashboard — log in through Okta as the workforce IdP, federated over OIDC to Microsoft Entra ID so Azure resources see a first-class Entra token with the group claims that drive Cosmos/API RBAC and Power BI row-level security. The customer-facing track page is anonymous-but-tokenised: a tracking link carries a signed, scoped token validated at API Management so a customer sees one parcel, not the fleet.

Stream Analytics wiring. Keep the hot job lean: location and scan inputs, reference-data joins for route and parcel mapping, hopping windows for speed and ETA, and an anomaly query for dwell/missed-scan/cold-chain breaches. A representative ETA-and-geofence shape:

SELECT
    parcelId, shipmentId, region, route,
    System.Timestamp() AS asOf,
    AVG(speedKph) OVER (PARTITION BY parcelId LIMIT DURATION(minute, 10)) AS avgSpeed,
    udf.etaMinutes(lat, lon, route, AVG(speedKph)) AS etaMinutes,
    udf.geofenceHit(lat, lon, route) AS geofence
INTO cosmosGeoState
FROM telemetry TIMESTAMP BY eventTime
PARTITION BY region;

Output the same rows to the notification topic (filtered to geofence hits and ETA-slip events) and to the BI feed, so one job feeds reaction, serving, and reporting without re-reading the stream.

Enterprise considerations

Security & Zero Trust. The architecture is Zero Trust by construction: every device has a unique, revocable identity; humans reach data only through Okta→Entra with least-privilege RBAC and conditional access; the customer API is fronted by Akamai (TLS, anycast, WAF, bot mitigation against scrapers hammering the public track endpoint) and API Management (JWT validation, per-client rate limits, response caching of hot lookups). Layer on: Wiz running continuous CSPM and sensitive-data-exposure scanning across IoT Hub, Storage, and Cosmos — alerting the moment a resource drifts to public exposure or an over-broad role appears — with Wiz Code catching the same class of mistake in IaC before it ships; CrowdStrike Falcon sensors on the AKS node pool that hosts the API and on the on-prem virtual-appliance gateways, feeding runtime detections to the SOC; and a fleet-level alert — say a whole depot’s trackers going dark at once, which can signal a network attack or a misconfiguration — auto-raising a ServiceNow incident with the affected devices pulled from the CMDB. Azure Policy denies any IoT Hub, Storage, or Cosmos account created with public network access; Wiz independently verifies the policy is holding.

Cost optimization. Streaming spend and RU/s dominate, and both scale with traffic, so engineer for it from day one.

Lever Mechanism Typical effect
Route at ingestion IoT Hub splits heartbeats/raw to the Data Lake, off the streaming tier Keeps the expensive path to events that matter
Edge pre-aggregation IoT Edge filters/aggregates at the sort centre before the WAN Cuts cloud message volume and IoT Hub units
Cosmos TTL + tiering Delivered parcels age out of hot store to ADLS Bounds RU cost to active shipments, not all history
Autoscale, not provisioned-for-peak Stream Analytics SU and Cosmos RU autoscale to demand Pay peak only at peak, cheap overnight
Storage lifecycle ADLS hot→cool→archive on the audit/replay data Slashes the cost of the long tail you must keep

Meter ingestion volume and RU consumption per region and per client, and pipe the metrics to Datadog (or Dynatrace) for the chargeback view the marketplace client’s account team and the carrier’s CFO both watch.

Scalability. Each tier scales independently. IoT Hub scales by units (messages/sec) and is partitioned internally; size units to peak ingest. Stream Analytics scales by streaming units and parallelises only if the query is partition-aligned — partition the job by region end to end or the SU increase buys nothing. Event Grid absorbs millions of events/sec natively. Cosmos scales by adding RU/s (autoscale) and, for a fleet that spans countries, multi-region with the write region near the bulk of devices. The natural ceiling is Stream Analytics SU per job, which is why a national rollout shards jobs by geography early rather than running one monster job.

Failure modes, and what each one looks like. Name them before they page you.

Reliability & DR (RTO/RPO). Decide the numbers per tier. Cosmos DB with multi-region writes gives near-zero RPO and seconds RTO for geo-state. The Data Lake is the durable source of truth (geo-redundant), so the entire geo-state can be rebuilt by replaying Capture if Cosmos is ever lost — the real recovery guarantee. IoT Hub supports manual failover to its paired region; DPS re-points devices. Stream Analytics jobs are redeployable from IaC in the paired region and resume from the input’s retained window. A pragmatic target for this platform: RTO 15 minutes, RPO under 1 minute for live tracking, with full history reconstructable from geo-redundant storage. Akamai health checks drive edge failover for the public track page so customers never see a hard error during a regional event.

Observability. Instrument the pipeline end to end in Dynatrace (or Datadog) and emit the metrics the business cares about, not just CPU: ingestion lag (device event time → Cosmos write, the freshness SLA the whole project exists to fix), ETA accuracy (predicted vs actual delivery, tracked per lane), dead-letter depth (events Grid could not deliver), per-region message rate, and stale-device count. A latency or accuracy regression should surface on its own through anomaly detection, not in a customer complaint. Device-fleet changes and new client onboardings pass a ServiceNow change gate, giving operations a documented record.

Governance & enablement. Pin Stream Analytics query versions and Cosmos indexing policy in version control, reviewable and revertable. Apply Azure Policy to deny public network access and require diagnostic settings on every IoT, Storage, and Cosmos resource, with Wiz as the independent check the controls are real. The audit trail in the Data Lake is the system of record for “where was parcel X at time T,” retained per the client contract with a lifecycle policy. And the human side is not an afterthought: depot operators and drivers are trained on the scanner app and exception-handling flow in Moodle, with course completion gating device issue — because a track-and-trace platform is only as accurate as the scan discipline feeding it.

Explicit tradeoffs

Accept these or do not build it. An event-driven IoT pipeline adds real moving parts — a device-identity and provisioning estate, an event router with dead-letter handling, a stream job whose partitioning you must get right, and a geo-state store whose partition key is a one-way door. Eventual consistency is inherent: the dashboard reflects reality within seconds, not instantly, and a buffered device backfills history after the fact, so any view is “best known so far.” ETA is a prediction; the stream keeps it fresh but cannot make a truck stuck in unmodelled traffic arrive on time — you commit to measuring accuracy and tuning, not to perfection. And the cost discipline that keeps this affordable (route at ingestion, edge pre-aggregate, TTL the hot store) is engineering you have to do up front; skip it and the bill scales linearly with a nine-million-parcel firehose.

The alternatives, and when they win. If your volume is modest and a few minutes of lag is acceptable, Event Hubs straight into a Functions consumer without IoT Hub’s device management is simpler — you lose per-device identity, offline buffering, and cloud-to-device control, which a managed fleet cannot give up but a fixed set of trusted gateways might. If you need heavyweight stateful stream processing — complex event correlation across long horizons — Azure Databricks structured streaming or Apache Flink outpowers Stream Analytics at the cost of an always-on cluster and far more operational weight; reach for it only when the SQL windowing model genuinely cannot express your logic. And if tracking is one feature of a broader IoT estate (cold chain, predictive maintenance, asset utilisation), Azure IoT Operations / the wider IoT platform is the strategic home; this architecture is the focused, cost-aware build for the track-and-trace problem specifically.

The shape of the win

For the carrier, the payoff is not “a nicer map.” It is that the marketplace client opens an embedded dashboard and sees their shipments, on-time performance by lane, and an at-risk queue that flagged the broken-down truck at 9:05 a.m. — minutes after it stopped, not in tomorrow’s report — so a recovery van was dispatched before the SLA breached and the account stayed. It is that “where is my parcel” answers itself in the app with a live position and an ETA the customer trusts, so the call never reaches the contact centre whose cost exceeded the shipment’s margin. Everything upstream — the IoT Hub device identities, the ingestion-time routing, the Event Grid switchboard, the Stream Analytics ETA, the Cosmos geo-state, the Azure Maps geofences, the Wiz posture scanning, the Vault-held signing keys, the Dynatrace lag metric — exists to turn a day-late batch into a live, trustworthy, fleet-scale truth that a board, a marquee client, and a CFO each say yes to. Start narrower if you must, but this is where real-time track-and-trace has to land.

AzureIoT HubEvent GridStream AnalyticsCosmos DBLogistics
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading