Tidewater
A tracking service that turns every carrier’s feed into one honest timeline per shipment.
Context
Carriers send status updates in different formats and at different times, sometimes twice and sometimes out of order. The customer portal stored a single status field and overwrote it on every message, so customers saw shipments jump backwards and called support to ask which status was true.
Idea
Stop overwriting. Every carrier message becomes an immutable event, and the timeline is derived from the events. Order comes from the time the carrier says something happened, not the time we heard about it, and a late message simply causes that one timeline to be rebuilt.
| Row | Received | Occurred | Carrier | Event |
|---|---|---|---|---|
| 0000 | 09:02 | 08:55 | Carrier A | Picked up |
| 0010 | 09:40 | 09:31 | Carrier A | Departed hub |
| 0020 | 10:15 | 07:58 | Carrier B | Booking confirmed |
| 0030 | 11:03 | 10:47 | Carrier A | Arrived at hub |
| 0040 | 11:09 | 10:47 | Carrier A | Arrived at hub |
Details
Idempotent ingest
Each event gets a key hashed from carrier, reference, code and event time. Duplicates hit a unique index and disappear, so retries are safe everywhere.
A small shared vocabulary
Carrier codes map to eleven internal states through one table that support staff can read. New carriers are a data change, not a deploy.
Rebuild, never patch
When an event arrives late, the service re-derives that shipment’s timeline from scratch. It is cheaper than it sounds and it removed a whole class of ordering bugs.
const key = sha256([carrier, ref, code, occurredAt.toISOString()].join('|')); await db.query( `insert into events (key, shipment_id, code, occurred_at, raw) values ($1, $2, $3, $4, $5) on conflict (key) do nothing`, [key, shipmentId, normalize(carrier, code), occurredAt, raw],); await rebuildTimeline(shipmentId);Outcome
Shipped as the single source for the customer portal and for the support team’s internal view, so both now show the same history in the same order.