Backhaul
Truck load matching and freight visibility for Nigerian road logistics.
- React Native 0.87
- TypeScript
- Kotlin
- Swift
- C# / .NET 9
- PostgreSQL
- Python (build gates)
The problem
Almost every load in Nigerian road freight is agreed on WhatsApp — a call, a yard, a chat window. Nobody involved can see where the truck is, and the shipper finds out it arrived when the driver rings.
Backhaul does not try to replace how loads get agreed. It lets a shipper type the two phone numbers they have been messaging and follow the truck from there. Tracking is the wedge; matching is the business — a marketplace is worth nothing until the tracking has earned somebody's trust.
What I built
- A native capture loop — an Android foreground service and iOS region monitoring, behind one TurboModule contract, with a SQLite queue that keeps positions when the network goes. Capture continues through signal loss on purpose: stopping loses precisely the stretch of road nobody can account for afterwards.
- Offline proof of delivery. At a market gate with no signal the driver photographs the goods, takes a signature and seals the delivery. The phone holds it and sends it when it can — because a delivery captured and lost is a driver who finished the run and is not paid.
- A .NET API held to the app’s rules by tests. Anything implemented on both sides — the trip machine, pricing, demurrage, settlement, fix cleaning — is compared against fixtures generated from the TypeScript. A disagreement fails the build.
- Four faces, one rules package. Shipper, carrier and driver in one binary, plus a browser console for shippers working from an office. The matcher that finds Port Harcourt from
port-harcourtis the same function in all four. - Four languages. English, Hausa, Yorùbá and Igbo, with a build gate that fails on any hard-coded string — 27 screens, zero.
Decisions worth naming
- No estimate is presented as a measurement. The arrival window is a range, never a point, and it refuses outright rather than guessing — including whenever an incident is blocking the road. Every refusal carries the sentence that says what would fix it.
- A trip history is append-only. No update path, no delete path. A correction is a new event and the original survives, because the history is evidence.
- Money is integer kobo, shown in whole naira, rounded per line so a settlement adds up on screen as well as in the arithmetic.
- The device seals a delivery; the server countersigns. Two timestamps, kept as two facts — when it happened and when the platform first saw it. A dispute wants the gap, and a coverage hole explains it.
- A phone number names a party and never answers a question. Opening a trip resolves numbers to accounts and replies identically whether it found one or made one, so the endpoint cannot be walked to learn who is on the platform.
- A tier is earned from evidence the carrier cannot write. Papers count once a reviewer has confirmed them; the ladder used to read flags the carrier set themselves.
What is not finished
- A device day. Battery under 4% per hour and a 72-hour soak, on a physical Tecno or Infinix. Transsion power management is aggressive and undocumented, and it is the assumption the most work rests on. No simulator signs it off.
- A native speaker. Roughly 640 keys each in Hausa, Yorùbá and Igbo, written by somebody who speaks none of them. The gate proves every string reaches a screen; nothing proves one is right.
- Push credentials. The rule, the dispatcher and the quiet hours are built and tested; the sender writes to a log saying it did not send. APNs wants a p8 from an Apple developer account, FCM a service-account JSON.