A food delivery can be flawless for thirty minutes and fail in the last ten metres. The rider marks it delivered at the wrong door. Someone insists it never arrived. A handover gets recorded that didn't happen. On Saturdays — a food delivery platform where riders carry real, paid orders — the handover is verified with a one-time code, and the interesting engineering is in everything around those six digits.
Why the handover needs proof
Nearly every other step of an order produces a system record: the payment settled, the restaurant accepted, the rider was assigned. The handover is the one moment that depends on two people standing at a door, and "delivered" is just a button on the rider's phone. Without proof, a dispute is one person's word against another's — and the platform, which is holding the money, has nothing to decide with.
A handover OTP makes that moment verifiable. The customer has a code, the rider has to enter it, and "delivered" is only recorded when they match.
Six digits is not the security
The obvious objection: six digits is a million possibilities, and a script can try a million things. That's correct, which is why the code on its own isn't the control. The design around it is.
Store a hash, never the code. The OTP is hashed at rest, the same way you'd treat a password. Anyone who reads the database — a leaked backup, an over-privileged query, an internal tool — sees nothing they could hand to a rider.
Lock out after repeated wrong entries. This is what actually makes six digits sufficient. A million possibilities is trivial with unlimited attempts and hopeless with a handful. After repeated failures the handover locks, and a locked handover is a signal worth surfacing, not just a counter to reset.
Verify on the server. The rider app submits a guess and the server compares it. The code is never sent to the rider's device to be checked locally, because a check that runs on the client is a check the client can skip.
def verify_handover(order, submitted: str) -> bool:
otp = order.handover_otp # hash, attempt count, locked flag
if otp.locked:
raise HandoverLocked(order.public_id)
if not check_code(submitted, otp.code_hash):
otp.register_failure() # may lock after repeated misses
return False
otp.consume() # single use
order.transition_to("delivered") # still goes through the state machine
return True
(A simplified sketch of the flow.)
Look at the last line. A correct code doesn't just set a field — it moves the order through the same state machine as every other transition, so "delivered" still has to be a legal next state.
One OTP system, not two
Saturdays isn't the only product in the order flow: restaurants running the DineGuru POS work the same orders. The easy path would have been an OTP implementation in each product. Two implementations means two hashing choices, two lockout policies, two sets of edge cases — and eventually two different answers to "is this handover valid?"
So there's one OTP system, shared across both. Change the attempt limit or how a lock is cleared, and it changes once, everywhere.
The takeaway
When the weakest moment in a workflow is a human interaction, don't try to make it more trusted — make it verifiable. A short code is enough when it's hashed at rest, rate-limited by lockout, checked on the server and tied into your state machine. The strength comes from the system around the secret, not the length of it.
The rest of the delivery flow — live tracking, the order state machine, doorstep payment collection — is in the case study.
👉 Full case study: Saturdays — Food Delivery Platform
Divyakush Punjabi — Full-Stack & AI Systems Engineer
🌐 https://www.divyakush.com · 💼 LinkedIn · 💻 GitHub
Top comments (0)