The hard part is not the map. It is what carries the update, how often a phone may speak, and what happens to the screens that were closed when the status changed. Four products here answer differently: Bix Delivery, Antrak, The Shipping Hack and 3DLogistiX. Two run their live layer on Firebase, two on WebSockets over Redis, and the split follows the shape of the product. Commercial scope sits with Sigi’s logistics practice.
What is real-time tracking app development?
Real-time tracking app development is the design and build of the pipeline that carries a position or status change from one device to every other client that cares about it, plus the storage, permissions and battery budget it needs. Any one of its four layers can undo the other three.
- RFC 6455 gives the reason WebSockets exist: HTTP polling forced a server to hold several TCP connections per client, with an HTTP header on every message.
- Server-sent events are one way. MDN states you cannot send events from a client to a server, so they cannot carry a driver’s position reports.
- Firebase and WebSockets sit at different layers. Firebase is a managed backend whose clients synchronize over a socket; WebSocket is a protocol you operate.
- Apple documents that iOS suspends most apps shortly after backgrounding and queues location updates until the app runs again, so background delivery is a platform decision.
- Redis Pub/Sub is at-most-once and a missed message is forever lost, so socket fan-out needs a resync on reconnect, not only a broadcast.
WebSockets vs polling vs server-sent events: which transport fits?
A transport is what moves an update from server to client. RFC 6455, the WebSocket standard, states the problem it was written for: with polling, the server is forced to use a number of different underlying TCP connections for each client, and each client-to-server message carries an HTTP header. Its answer is a single TCP connection for traffic in both directions.
- WebSockets when both ends push. A driver app reports position and receives job offers on one connection, and you own reconnection and fan-out.
- Server-sent events when only the server pushes, which fits a customer tracking page or an operations dashboard, and the browser reconnects on its own. MDN warns that outside HTTP/2 the limit is six open connections per browser and domain; over HTTP/2 it becomes the negotiated stream count, defaulting to 100.
- Polling when a status changes every few minutes and nothing else does. A parcel crossing eight states in three days needs no socket on the customer view.
WebSockets vs Firebase: what four shipped products chose
Most comparisons stop at the abstract. Here are four real decisions. On Bix Delivery, a courier service with Flutter customer and driver apps over a Node.js backend, Firebase handles push notifications and the real-time channel that keeps location and status flowing to both apps. On Antrak, a UK courier platform of the same shape, Firebase Cloud Messaging carries push and driver online and offline presence, with MongoDB holding the geospatial index behind the job feed.
The two multi-surface platforms went the other way. On The Shipping Hack, a parcel forwarding platform with five surfaces over one backend, each state transition is published through Redis to WebSocket connections held open by every client that cares about that parcel. On 3DLogistiX, an Australian warehouse system where Sigi supplied embedded engineering capacity, Node.js microservices use MongoDB, Redis for hot state and pub/sub, and WebSockets to push changes to browsers and handsets.
Check the ceiling first. The Firebase Realtime Database limits page puts simultaneous connections at 200,000 per database, counting every device, tab and server app. Generous for a courier fleet, thin for a consumer product.

Presence as intent: the driver declares availability rather than the server inferring it

Push carries the status change; the app renders the state the server reports
How often should a device send location, and what does it cost in battery?
Live location updates are the most expensive part of a tracking product, and the cost lands on someone else’s battery. Apple’s desiredAccuracy guidance is to reduce your app’s impact on battery life by assigning a value appropriate for your usage. Android’s battery guidance for location is blunter: pass the largest possible value to setIntervalMillis, reserve intervals of a few seconds for foreground use, and keep high accuracy for foreground apps that need real time updates.
Two mechanisms buy most of that back. Batching is the first, through setMaxUpdateDelayMillis, which delays delivery so several updates arrive together. In the journal Sensors, Galeana-Zapién and colleagues report that individual transmissions demand around 40% of battery resources for a 12-hour lifetime, while batches of 32 GPS readings consume only 15% for the same lifetime. Accuracy is the second. A 2026 Sensors test protocol by Schweizer and colleagues found that reducing location accuracy increased battery life by up to 20 hours on the iPhone 13 Pro devices measured.
What the platforms allow in the background
Every claim here is a platform rule, not a preference. Apple documents that iOS suspends most apps shortly after they move to the background, that suspended apps receive no location updates, and that the system enqueues updates and delivers them when the app runs again. Continuous tracking needs the Location updates background mode. The lighter alternative, significant location change monitoring, has published bounds: a notification once the device moves 500 meters or more, and no more often than every five minutes.
Android is explicit too. Google’s background location page states that on Android 8.0 and higher an app in the background receives location updates only a few times each hour. A driver app therefore runs a foreground service, and from Android 14 it must declare the location service type and hold FOREGROUND_SERVICE_LOCATION, with coarse or fine location granted at runtime. Treat the permission prompt as a product surface: the feature is worth nothing if the driver declines.
How do push notifications fit alongside a live connection?
Push covers what a socket cannot: nobody is looking. Firebase Cloud Messaging separates notification messages from data messages, where the SDK displays the first automatically and the app processes the second, and caps both at 4096 bytes. Priority matters as much as payload. FCM documents that normal priority messages may be delayed in Doze mode, while high priority attempts immediate delivery, waking a sleeping device and allowing very limited network access. It also warns that messages which do not result in user-facing notifications may be deprioritized to normal.
So treat a push as a doorbell carrying an identifier, then fetch authoritative state over the live connection. Set a time to live that matches the event: FCM stores an undelivered message for a default of four weeks, and a job offer arriving an hour late is worse than none. Bix works this way: a confirmed delivery is offered only to drivers who have toggled themselves online and are near the pickup, the first to accept is locked to the job and the offer is withdrawn from the rest.
How do you know whether a driver is really online?
Presence is the system’s answer to whether a user or device is reachable right now. Tracking products need two answers. Intent is whether the driver has declared themselves available. Reachability is whether a connection exists. Conflating them offers a job to a phone in a locker.
Bix separates them on purpose: driver availability is an explicit online and offline toggle rather than an inferred signal, which keeps assignment honest. Antrak does the same, with Firebase Cloud Messaging carrying driver online and offline presence. For reachability, Firebase publishes a mechanism worth copying: a client reads /.info/connected, and an onDisconnect operation lives on the server, which monitors the connection and invokes the write when it times out or closes. The self-hosted equivalent is a heartbeat refreshing a short-lived key, so a process killed without a clean close expires rather than leaving a ghost online.
How do you find the drivers or jobs near a point?
Proximity is a database question, not a socket question. MongoDB’s geospatial documentation says to store location data as GeoJSON objects for geometry over an Earth-like sphere, longitude first and then latitude, and to use a 2dsphere index for spherical queries because 2d indexes can produce errors on them. The operators $near and $nearSphere require that index, $geoWithin does not, and the $geoNear aggregation stage does.
The Antrak job feed is exactly this query. Rather than broadcasting every job to every driver, the build passes a driver’s current location and self-chosen radius to MongoDB, which returns only unassigned jobs whose pickup falls inside that circle. The reason is operational: broadcasting produces a race for the easy runs and a backlog of unaccepted ones, while a radius feed leaves fewer stale jobs on the dispatch board. Where positions churn faster than a durable store should absorb, Redis geospatial indexes hold coordinates for radius and bounding-box search.
How do you keep every app showing the same status?
State synchronization across apps is the requirement behind everything above, and the answer is a single authority rather than a clever merge. On Bix, every delivery is a state machine on the backend and the apps only render the state the server reports: created, driver assigned, picked up, in transit, delivered, canceled. On The Shipping Hack, the parcel is a state machine whose every state carries the warehouse or vehicle responsible for it, which lets four views agree without reconciliation.
Two details separate a demo from production. First, resync on reconnect. Redis Pub/Sub is at-most-once and a missed message is forever lost, so a reconnecting client must fetch current state; Redis names Streams when stronger guarantees are needed. Second, protect the renderer. On 3DLogistiX the 3D view paints inventory onto instanced geometry through per-instance attributes, so a movement arriving over WebSockets updates a buffer instead of rebuilding meshes, and updates are batched into animation frames so a burst of scans is not a burst of re-renders.
How do you scale a socket layer past one server?
A WebSocket connection is state pinned to one process, which is why the second server breaks naive designs. The fix is a shared bus: publish every event to Redis and let each node deliver to the clients it holds, which is why a connection on one Shipping Hack server receives events produced on another. In cluster mode, sharded Pub/Sub, added in Redis 7.0, restricts propagation to one shard rather than every node. Route reconnections with sticky sessions, or make any node able to serve any client.
Then test the fan-out, not the endpoint. On 3DLogistiX the socket synchronization between handsets and browsers is exercised in the automated suite, and on The Shipping Hack the Playwright suites cover the multi-role flows where a warehouse scan must appear on a customer’s tracking page. A single-client test passes on a layer that silently drops cross-server events.
What does a real-time tracking layer typically cost to build?
Real-time is a property of the whole product, not a line item. As a planning estimate, a production iOS and Android app with authentication, payments and a light admin sits in the $80k to $180k band; a multi-surface platform with customer, worker and operations surfaces sits in the $150k to $350k and above band, per Sigi’s guide to how much it costs to build a mobile app. Those are typical-scope figures, never a quote or a number attached to a client. Background tracking pushes toward the upper band, because it adds permission flows, store review and battery testing.
In what order should you build it?
- Model the state machine first and make the server the only authority. Clients render a state they were told; none computes one.
- Pick the transport per surface: sockets where both ends push, server-sent events for view-only dashboards, plain requests where status changes a few times a day.
- Add push as a separate path with its own time to live, carrying identifiers rather than payloads.
- Set the location policy: interval, accuracy and batching per app state, with a foreground service on Android and the background mode on iOS only if the product needs them.
- Add presence as two signals, declared availability and connection reachability, with a heartbeat so a dead process expires.
- Scale fan-out with a shared bus, and test the cross-server case in continuous integration.
Related reading
For the product view, read how to build a delivery app and how to build a food delivery app. Inside the warehouse, how to build a warehouse management system covers the workflows behind those messages, and multi-tenant SaaS architecture explains why every socket channel needs a tenant identifier. Flutter vs React Native weighs the client framework. The builds cited are the Bix Delivery courier platform case study, the Antrak courier platform case study and the 3DLogistiX warehouse platform case study. To scope a tracking layer, start with Sigi’s logistics software practice, mobile app development or custom software development, or talk to Sigi.
