The Flipside
Cloud 30 August 2026  · 6 min read

Building Vehicle Telematics on AWS IoT Core: What the Docs Don't Tell You

Most IoT tutorials assume a device on a desk with reliable wifi. Ours were wired into vehicle ignition circuits, driving through parts of New Zealand with no coverage at all, and doing something a customer would notice if we got it wrong.

That changes which parts of AWS IoT Core matter. Here's what we'd tell someone starting the same project.

The single most important idea: shadows, not commands

If you take one thing from this: stop thinking about sending commands to devices.

The instinct is a POST /device/{id}/disable that goes out over MQTT and does the thing. It works on your desk. In the field it produces a system nobody can reason about, because "the message was sent" and "the device did it" are completely different claims and you've built something that conflates them.

AWS IoT's Device Shadow is a JSON document per device with desired and reported sections. You write intent to desired. The device — whenever it next connects, which might be now or might be Thursday — reads the delta, acts on it, and writes back what it actually did to reported.

That single indirection buys you most of what you need:

  • Offline is no longer a special case. A device out of coverage for two days isn't a failed command, it's an unreconciled shadow. Same code path.
  • The system has a truthful view. desired is what you want. reported is what's real. Anything that matters keys off reported, and the gap between the two is your operational dashboard.
  • Retries stop being your problem. No queue of pending commands to expire, dedupe or replay.
  • It's idempotent by construction. State converges. Commands accumulate.

The corollary is that anything acting on device state — billing, alerts, a support screen — must read reported, never desired. That rule sounds obvious written down and is violated constantly, usually by a dashboard that shows what someone asked for as though it had happened.

Provisioning is a bigger project than you think

Every device needs its own X.509 certificate, and "just register them" doesn't survive contact with the actual process. Someone is installing hardware in a vehicle, in a workshop, possibly with poor connectivity, and they are not going to run a script.

Two approaches worth knowing:

Fleet provisioning by claim. Devices ship with a shared claim certificate that can do exactly one thing: exchange itself for a real, unique device certificate on first connect. The claim credential is low-value by design, which is the point, because it's the one sitting in your factory image.

Just-in-time provisioning (JITP/JITR). Devices are manufactured with certificates signed by your own CA, which you register with AWS. First connection triggers registration automatically.

Either way, decide before hardware exists. Retrofitting identity onto deployed units means physical access to every one of them, which for vehicles means booking them in, which means it will not happen.

The other half is policy scoping. It is very easy to write one IoT policy that lets any device publish to any topic, and it means one extracted certificate compromises the fleet. Use policy variables so a device's own thing name gates its topics — ${iot:Connection.Thing.ThingName} — so a compromised unit stays exactly one compromised unit.

Design for the network you actually have

Assume the worst connectivity you can imagine, then assume worse.

Buffer on the device, always. Telemetry that only exists in transit is telemetry you lose. Ring buffer to flash, replay on reconnect, and make sure your ingestion is idempotent because you will get duplicates.

Timestamp on the device, not on arrival. A message that arrives Thursday describing Tuesday must say Tuesday. This sounds trivial and it is the single most common data-quality bug in telematics — you find it six months later when someone asks why the trip history is wrong.

Choose QoS deliberately. QoS 0 for high-frequency position updates you can afford to lose. QoS 1 for anything with consequences. There is no QoS 2 in IoT Core, so exactly-once has to be your problem, solved with idempotency rather than hoped for at the transport.

Watch the connection lifecycle. Cellular devices reconnect constantly. Connect/disconnect events are noise, not signal — a unit is not "offline" because it dropped a connection, it's offline because it hasn't reported for longer than your threshold. Get that distinction wrong and you'll build an alerting system that cries wolf.

Fail-safe means the device decides

This is the part that generalises past vehicles to anything consequential.

The cloud must never be the thing that makes a system safe. If losing connectivity, a bad deploy or a wrong record in a database can cause a harmful physical outcome, the architecture is wrong regardless of how carefully the code is written.

Concretely, for anything that actuates in the physical world:

  • The device enforces its own preconditions. It doesn't act on a state change because the cloud said so; it acts because the cloud asked and its own local checks pass. If those disagree, local wins.
  • The safe outcome is the default. Ask what happens with no connectivity, a corrupted shadow, and a half-applied update. If any of those answers is "the dangerous thing", start again.
  • Nothing consequential happens in motion. Physical state changes wait for a safe condition the device itself verifies.
  • Local override always works. Someone on site can put the device into a known-safe state without the network.
  • It's auditable end to end. Who set the desired state, when the device applied it, what it reported back. For anything with a contract behind it, this isn't nice-to-have.

The cost model bites at the wrong moment

AWS IoT Core bills mainly on messages and connection time, with messages metered in 5KB increments. Two consequences people meet late:

Chatty devices are expensive. A position update every 10 seconds across thousands of vehicles is a large number multiplied by a small number, and it's the same architecture as one every 60 seconds. Sample at the rate the business needs, not the rate the hardware can produce, and prefer event-driven reporting — ignition on, geofence crossed, threshold breached — over fixed intervals.

Basic Ingest is close to free money. If a message only needs to reach the rules engine and doesn't need to be seen by other subscribers, publishing to $aws/rules/<rule-name> skips the message broker and its per-message charge. On high-volume telemetry that's a large line item removed for a topic-string change.

Check current pricing before modelling anything — the rates move, the shape doesn't.

Things we'd do the same way again

  • Fleet indexing from day one. Being able to query "units that haven't reported in 48 hours" rather than investigating it changes how the support team works.
  • OTA updates as managed jobs, rolled out in waves, with a stop button. You will ship firmware that's wrong. Plan the rollback before the rollout.
  • Device health as first-class telemetry. Signal strength, voltage, uptime, error counts. When something is wrong in the field this is the difference between a diagnosis and a site visit.
  • One integration into the business systems, not a separate console. Telemetry nobody acts on is an expensive hobby. The value appears when device state reaches the team that does something about it — which is systems integration work, and usually the larger half of the project.

The honest summary

IoT Core is a good choice when you need device identity, offline state reconciliation and per-device authorisation — which is to say, when you have real devices doing real things. If you're moving telemetry between servers you control, a plain MQTT broker is simpler and cheaper.

The hard parts of a project like this were never the cloud service. They were provisioning at install time, being honest about connectivity, and making the physical outcome safe without depending on the network. There's more on how we work with AWS IoT Core, and the vehicle finance platform this came out of.

If you're scoping something with hardware in it and want a second opinion before committing to an architecture, that's a conversation we're happy to have.

AWS service behaviour and pricing change. Verify against current AWS documentation before designing to any specific limit or rate.

Enjoyed this post? Share it

Fresh thinking, delivered occasionally

New Flipside posts and the odd genuinely-useful insight. No spam, unsubscribe anytime.

Working on something like this?

We build this stuff for a living — and we're happy to talk it through, no pitch required.

Book a Discovery Call