Micromobility Platforms: The Ride Is a Distributed Transaction
The unlock and ride lifecycle, when a ride actually starts, curb economics, battery swap logistics, and why the payment authorisation is the hardest part of a scooter.

A shared scooter looks like a simple product. Scan, ride, park, pay. Behind it is a vehicle that may be out of battery, a payment authorisation, a set of geofences drawn by a city, a technician with a van, and a state machine that has to stay correct while all of it is happening simultaneously at six rides a second.
The interesting engineering is entirely in that state machine and the three systems it has to keep consistent: the app, the backend, and the vehicle.
The network, for scale
A single city operation with 25,000 vehicles.
vehicles = 25,000
rides per vehicle per day = ~8
rides per day = 200,000
average ride = ~5 km, so ~40 km per vehicle per day
peak: roughly 20% of rides in the 2-hour evening peak
40,000 rides / 7,200 sec = ~6 rides per second
Six rides a second sounds small. The operation is not small, because every one of those rides involves a physical vehicle moving through a city that does not have unlimited space for it.
The cost side, which is where the real planning happens:
gross: 200,000 rides x $3.00 = $600,000/day
fees: 2.9% + $0.30 per ride = ~$77,000/day
~13% of gross
Thirteen percent of gross revenue going to payment processing is not a rounding error, and it is the reason per-ride pricing, passes, and memberships exist. They are not marketing features. They are a payment cost strategy.
The state machine
Every vehicle is in exactly one state, and the transitions are the product.
reserve unlock confirmed
[available] ----------------> [reserved] ----------------> [unlocked]
^ | timeout |
| v v
| [cancelled] [in ride]
| |
| park confirmed lock confirmed |
+--------------------------------------------[parked]---+
[charging] --(swap complete)--> [available]
[out of service] --(repair)--> [available]
[decommissioned] terminal
The states that matter and are usually under-specified:
Reserved. A user has claimed the vehicle and has a timer. The vehicle is not available to anyone else, and the reservation has to expire. This is a payment hold and a timeout, and the timeout is a place where money has been taken and must be released.
Unlocked but not yet in a ride. A vehicle that is unlocked but the user has not moved. This is the awkward state and it is covered below.
Out of service. A technician flagged the vehicle. It is still visible in the app, marked, and it is not rentable. Getting this wrong means a user walks to a broken vehicle.
Decommissioned. Terminal, and it has to stop appearing in the map while its location history is retained for investigations.
Unlocking is the whole product
The flow a user experiences as a scan is four systems agreeing on something.
1. app scan the QR on the handlebar
2. app POST /unlock { vehicle_id, payment_method, client_location }
3. backend authorise the payment -> hold on the card
4. backend verify the vehicle is available, in a legal zone, not broken
5. backend publish an unlock command to the vehicle
6. vehicle actuate the latch, confirm over MQTT
7. backend mark the vehicle in-ride, start the clock
8. app show the vehicle
The budget is under two seconds, and every step can fail independently.
The vehicle has moved since the map was drawn. Someone else unlocked it thirty seconds ago. This is a normal, frequent event on a popular vehicle, and the response has to be a clean, immediate “someone just took this one” with a suggestion of nearby alternatives, not a pending state that eventually times out.
The app’s GPS is wrong. Phones report a location that can be thirty metres out, and it is worse indoors and in dense cities where multipath dominates. So the app cannot be the authority on which vehicle the user is near. The QR code identifies the vehicle, and the app’s location is a hint for ranking nearby vehicles, not for unlocking. Getting this backwards produces a failure where the user is standing in front of a scooter and cannot unlock it.
The vehicle has no signal. Underground, in a metal box, in a crowd. The command is queued in the broker with a short expiry, and the app shows a bounded wait rather than an indefinite spinner. If the vehicle is offline, the vehicle’s reported state should already say so, and the app should have greyed it out before the user walked over.
The battery is flat. The vehicle reports battery level. Below a threshold, it should be marked as requiring a charge and offered for rental only if the remaining range covers the expected ride, or not at all.
The payment fails. A card decline at a scooter is a stranded user and a stranded vehicle. The design answer is authorisation before commitment, with a fallback payment method attached to the account, and a decision about what happens to the vehicle if the payment fails after the latch is already open. That last case needs a rule, and the rule has to be written down: refund the hold, log the ride as unpaid, and let a collection process handle it, or relock the vehicle.
When does a ride actually start?
This looks like a small question and it is a fairness decision with battery, latency, and revenue consequences.
Billing from the unlock. Simple, matches user expectation, and the fare clock starts the moment the latch opens. Its failure mode is specific and infuriating: a user scans a scooter whose location pin is wrong, walks twenty metres to the real one, and pays for that walk. Or a group of four shares one vehicle for the first minute.
Billing from first motion. Fair, and the fare matches the ride. Its costs are real: the vehicle must have motion detection awake, which affects battery; there is a window where the user is unlocked and not in a ride, during which they can be charged nothing and someone else could in principle reserve the vehicle; and the ride start has to be triggered and reported reliably, so a slow detection event shows the user a free ride that later becomes billable.
The design that works is a hybrid, and the hybrid is where the rules live:
unlock confirmed
-> vehicle is ARMED: latch open, short grace period
-> ride starts on first sustained wheel motion
-> if no motion within the grace period, the vehicle relocks itself
and the reservation is released
The grace period has to be long enough to cover a user walking around the vehicle, and short enough that an abandoned unlocked vehicle is not a loss. That is a tuning parameter with a real product consequence, and it should be one.
The other consequence of motion-based start is that the vehicle is awake during the grace period. On a fleet of 25,000 vehicles, every one of them waking up on a scan is measurable battery life, and the fleet’s energy bill has a line item that exists because of a product decision.
Geofences, and the difference between the easy half and the hard half
The geofence is the easy half. Draw polygons for the service area, the permitted parking area, and the no-ride zones, and reject any unlock or park outside them.
The hard half is clearing the pavement.
A geofence tells a vehicle where it may be left. It does not make the vehicle move. The failure mode is entirely physical: a scooter is left on a pavement, a cycle lane, a ramp, a doorway, or a fire hydrant, and somebody has to deal with it.
The approaches, in increasing order of effectiveness and difficulty:
Geofenced parking bays. Mark areas where parking is permitted and require the ride to end inside one. Simple, effective where the city will paint the markings, and it fails wherever markings are worn or the app’s geofence is off by a few metres.
Incentives. Pay users to finish a ride in a low-demand area, and penalise ending one in a high-demand zone. This is the mechanism that actually works, because it puts the redistribution labour on the people already riding, and it aligns with the fact that a vehicle’s most valuable moment is when it is somewhere somebody wants it.
Photo verification. Require or invite a photograph of where the vehicle was left, and use it for enforcement and for training a model. It adds friction at exactly the moment the user wants to stop, so it is better as an occasional check than a mandatory every time.
Computer vision. Cameras or vehicle cameras detecting misparking. Effective and expensive, and it needs the city’s agreement to be anything other than intrusive.
The design insight is that the geofence is enforcement and the incentive is distribution, and conflating them produces a platform that punishes users for the network’s failure to be where it should be.
Redistribution and the technician workforce
A vehicle that ends a ride in the wrong place is a vehicle that will not be rented again, and a vehicle at low battery is a vehicle that will not be rented at all.
The numbers set the scale of the operation:
25,000 vehicles
a swap roughly every two days of use
= ~12,000 battery swaps per day
a swap takes ~3 minutes of technician time
= 36,000 minutes = 600 technician-hours/day
at 8-hour shifts and 100% utilisation
= 75 technicians
allowing for travel, breaks, and vehicle search
= 150 to 250 in practice
A few hundred field staff is a real operating cost with recruitment, training, vehicles, insurance, and a route-planning problem of its own. It is a planning input, not something to discover during operations, and it is the reason battery health telemetry matters more than ride telemetry.
That telemetry is unglamorous and specific:
battery level over time, not just a snapshot
unexpected drain rate -> a failing cell
voltage sag under load -> a cell at end of life
charge cycles since new -> replacement schedule
time parked without a ride -> abandonment or a zone with no demand
battery temperature -> a hot battery is a safety issue
Deteriorating battery health is also the single biggest determinant of customer satisfaction, because a vehicle that dies two kilometres into a ride is a refund, a bad review, and a support contact. Predicting end-of-life from drain rate and cycle count and retiring a battery before it strands a user is worth more than any fleet analytics dashboard.
The economics of a ride
Worth stating plainly, because a scooter is a small business and the unit economics are unforgiving.
per ride, roughly:
gross $3.00
payment processing -$0.39
vehicle depreciation -$0.60 (vehicle life divided by total rides)
technician time -$0.55 (swaps and collections, apportioned)
energy and connectivity -$0.05
support and refunds -$0.15
city fee or revenue share -$0.40
------
contribution ~$0.86
Two things follow. First, passes and memberships are economically necessary rather than promotional: a monthly pass works because it converts unpredictable per-ride revenue into predictable subscription revenue and shifts the margin into volume, and because it removes the per-ride payment cost.
Second, utilisation is everything, and a vehicle that sits idle is a pure cost. The operational goal is not to maximise rides per vehicle at the cost of a worn fleet; it is to keep enough vehicles healthy and in the right places, which is the entire reason redistribution is a first-class system rather than a nightly batch job.
Safety and regulation, which are not soft
A micromobility platform operates in a domain where the rules are written by somebody else and can change on a month’s notice.
The requirements that a city or a jurisdiction can impose:
speed cap, per vehicle type, sometimes geographically variable
minimum rider age, and licence or permit requirements
helmet requirements
permitted and prohibited riding areas
sidewalk riding prohibitions
parking zones, with removal for violations
insurance minimums, and the question of who is liable
data reporting obligations, sometimes in real time
Two design consequences.
Speed caps belong on the vehicle. A soft cap enforced in the app is a suggestion, and a rider with the app in a pocket is not going to obey it. A hardware cap in the controller is real. This is a case where putting the control in the firmware is both the compliance answer and the safety answer.
Geofenced no-ride zones have to actually prevent the ride, not just tint a map. Some of them are transit corridors, school zones, and pedestrian areas where a rider putting a phone down will not see a soft boundary.
The liability question deserves a note because platforms have been surprised by it. There have been a small number of widely reported incidents in several jurisdictions, and the legal position on whether a shared-vehicle operator shares liability for a rider’s own negligence has not been settled consistently. A platform that assumes it has none is taking a risk it has not priced.
Insurance, therefore, is a product concern: coverage for the fleet, coverage for riders where offered, and a documented position on accidents that involve a third party.
Loss, theft, and geofencing as a security control
Vehicles are stolen. It is a material cost line and a safety issue, and the countermeasures are mostly about making theft impractical rather than about catching anyone.
geofence alarms leaving the service area raises an alarm and an immobilise command
immobilisation the motor will not run outside the geofence
motion tamper an accelerometer spike distinct from normal riding
periodic location enough to know where a missing vehicle is
battery as a tracker a stolen bike's battery is reported, which localises it
The last one is underrated: the battery management system reports, so a stolen vehicle continues to be locatable while it has charge, and the vehicle is inoperable outside the service area regardless. A vehicle that is both immobilised and tracked is close to a bounded loss, and the residual case is vehicles taken to a place where they are drained and abandoned, which is where the “battery as a tracker” behaviour becomes genuinely useful.
The design rule is that immobilisation must be enforced by the vehicle and must not depend on connectivity. A stolen vehicle with no signal should still be unable to start.
Failure stories worth testing
Scan a scooter that someone else just unlocked
The response must be immediate and clean, with a nearby alternative, and no payment hold left dangling.
Stand next to a scooter in a car park and scan the QR
The QR must win. If the app’s geolocation is the authority, this fails, and it will fail for users in exactly the places where the service is most useful.
Unlock with a card that is then declined
Decide what happens to the vehicle and to the hold, and test the relock path.
Unlock a vehicle at 4% battery
Either reject the rental or show the remaining range, and make sure the range estimate accounts for the return trip, not just the outbound one.
Unlock and walk away
The vehicle must relock itself after the grace period and the reservation must release. Test with a real clock, not a simulated one.
End a ride on a pavement outside every parking bay
The behaviour should be defined and it should not be a hard failure, because a hard failure at the end of a ride is when users leave vehicles in the worst possible places.
Drain a battery to zero in a parking zone
Confirm the vehicle reports itself as needing service and stops appearing in search results.
Steal a vehicle and take it outside the geofence
Confirm the immobilise command executes, the alarm fires, and it works with no connectivity.
Have the technician app lose connectivity mid-swap
A half-completed swap is the classic failure. It has to be resumable or safely abortable, and the vehicle’s state has to reflect reality afterwards.
Break a vehicle and have a user already walking to it
The app must reflect the out-of-service state, and the user must get a refund and a nearby alternative without asking.
A production-ready architecture
[ app ] --HTTPS--> [ API gateway ]
^ |
| v
| +------------------+
| | Ride service | the state machine
| | reserve, unlock, |
| | start, end |
| +--------+---------+
| |
| +--------v---------+
| | Payment | authorise, capture,
| | adapter | void, refund
| +------------------+
|
[ vehicle ] --MQTT--> [ Device gateway ] --> command + state
|
v
+---------------+
| Kafka |
+-------+-------+
|
+-------------+----------+-------------+-------------+
| | | |
+-----v------+ +----v-------+ +--------v------+ +---v---------+
| Position | | Telemetry | | Payment | | Analytics |
| (live) | | time series| | ledger | | (offline) |
+------------+ +------------+ +---------------+ +-------------+
[ technician app ] --HTTPS--> [ field operations service ]
|
+-------v--------+
| geofence rules |
| parking zones |
| no-ride zones |
| speed caps |
+----------------+
A sensible delivery checklist:
- Model the vehicle lifecycle as an explicit state machine with every transition and every timeout written down.
- Identify the vehicle by QR code, and use the app’s location only for ranking nearby vehicles.
- Authorise payment before committing, with a defined rule for a vehicle already unlocked.
- Use a motion-based ride start with a grace period, and make the vehicle relock itself if nobody moves.
- Separate geofence enforcement from redistribution incentives, because they solve different problems.
- Track battery health from drain rate, cycle count, and voltage sag, and retire a battery before it strands a user.
- Enforce speed caps in the vehicle firmware, not in the app.
- Make geofenced no-ride zones actually prevent the ride.
- Immobilise a stolen vehicle on the vehicle, and keep it locatable through its battery.
- Make technician operations a first-class service with resumable field actions.
- Build the offline runbook, because field work happens in places with no signal.
- Get the payment costs into the pricing model, because thirteen percent of gross changes which pricing strategies work.
Common mistakes
| Mistake | What actually happens | Better decision |
|---|---|---|
| App GPS decides which vehicle is being unlocked | A user standing in front of a scooter cannot unlock it | QR identifies the vehicle, geolocation only ranks |
| Billing from the unlock | The user pays for the walk to the vehicle and for group sharing | Motion-based start with a grace period |
| No grace period on an unlocked vehicle | Abandoned vehicles accumulate and lose battery | The vehicle relocks itself |
| Hard geofence rejection at ride end | Users abandon the vehicle in the worst place instead | Warn, allow, enforce separately |
| Geofence treated as the distribution strategy | Vehicles still end up nowhere useful | Pair enforcement with incentives |
| Speed cap in the app | A rider with the phone in a pocket is not going to obey | Cap in the vehicle firmware |
| Soft no-ride zones | A rider who is not looking at the phone rides through | The vehicle enforces the boundary |
| Snapshot battery reporting | A vehicle dies two kilometres into a ride | Drain rate, cycle count, and load sag |
| Immobilisation requiring connectivity | A stolen vehicle in a dead zone still runs | Enforce on the vehicle itself |
| Technician app with no resume | A half-completed swap corrupts vehicle state | Resumable or safely abortable field actions |
| Payment cost ignored in pricing | Thirteen percent of gross disappears and the model looks wrong | Model processing cost per ride and per pass |
| Unauthorised state changes | A vehicle appears available while it is in a ride | Single state machine, no direct writes |
| Out-of-service vehicles still in search | Users walk to a broken vehicle and refund | Exclude from search, show a reason nearby |
| No offline runbook for field ops | Technicians cannot work in the places the vehicles are | Printable, locally-served procedures |
| Assuming the vehicle reports its own state | The app is confidently wrong about a vehicle it cannot see | Track last-seen and degrade visibly |
The complete story in one minute
A user scans a QR code on a handlebar. The app sends an unlock request naming that vehicle, and the app’s own location is only a hint for ranking alternatives, because a phone’s position in a city is not reliable enough to decide which physical object is in front of the user.
The backend authorises a payment hold, verifies the vehicle is available, in a legal zone, and not out of service, and publishes an unlock command. The vehicle actuates the latch and confirms. The ride has started, the hold becomes a capture, and the whole sequence has a two-second budget across three systems.
The vehicle is now armed. When the wheels move, the ride begins. If nobody moves within the grace period, the vehicle relocks itself and the reservation releases, which is what stops a mis-scanned scooter sitting unlocked on a pavement for an hour.
Rides end in a parking zone, and a geofence rejects the ends that fall outside one, but the geofence is enforcement. Distribution comes from incentives that pay riders to finish a vehicle where the next rider will want it, because a geofence cannot move a scooter.
Batteries degrade predictably from drain rate, cycle count, and voltage sag, and a platform that retires a battery before it strands a user has done more for its business than any fleet dashboard. Theft is made impractical with a hardware speed cap, geofence-based immobilisation that works without a signal, and a battery that keeps reporting until it is drained.
And the economics come down to thirteen percent of gross going to payment processing, which is why passes and memberships exist, and to utilisation, which is the reason redistribution is a live system rather than a nightly batch.
That is the whole path:
scan -> authorise -> command -> armed -> motion -> ride -> park -> capture
The hard parts were never the map. They were a state machine that has to stay correct across three systems at six rides a second, a decision about when a ride starts, and the recognition that a shared vehicle is a physical logistics operation with a mobile app bolted on.
What this team still owns
The operator owns the authoritative ride state, not the phone, the public feed, or a delayed vehicle twin. Unlock and end-ride commands need command IDs, expiry, device acknowledgements, and a reconciliation state for “physical outcome unknown.” The public GBFS feed is a read-only projection of the operator’s latest knowledge; it must not be reused as the transaction path, and its age must remain visible to consumers.


