Attendance on a site with no signal
An attendance system that needs a network will be wrong on exactly the days it matters. Queueing punches is easy; the rules that keep the data honest are not.
Construction sites have poor connectivity by nature. They are basements, they are half-built structures full of steel, they are outside a town, they are behind a hill. Coverage is worst exactly where the work is.
So any attendance system that requires a live network will fail, and it will fail in a specific pattern: it works at the office, it works during the demo, and it produces gaps on the days people were furthest from a tower.
The engineering answer — queue the punches on the device and send them when the network returns — is straightforward. What is not straightforward is keeping the data honest once you have accepted records that were created while nobody was watching.
What offline actually means for a record#
A punch created offline carries three claims: a person, a place, and a time. All three were asserted by a device that was not in contact with anything at the moment of assertion.
That is a genuinely different kind of record from one created against a server, and it needs different rules.
Identity. The punch must authenticate as the device's own credential, never as an identity supplied in the message. If the payload says who it is from, then anybody who can reach the endpoint can be anybody. Our devices carry their own token, issued once, stored only as a hash, and the punch authenticates as that device rather than as whatever the message claims.
Time. The device clock is under the user's control. A punch that arrives claiming a time from six hours ago may be a genuine queued punch or a device whose clock was changed. Record both the claimed time and the received time, always, and let a person see the difference where it is large. Silently trusting one or the other is the wrong answer in both directions.
Place. A punch with no position fix should be refused rather than filed at a default location. We enforce this explicitly: no fix, no punch. The alternative is an attendance record that says somebody was at the point where the equator meets the prime meridian, which is worse than a missing record because it will not look missing.
The duplicate that arrives twice#
A flaky network is not a network that fails. It is a network that succeeds after the client has given up.
So the same punch will be delivered twice, or five times, and the system must fold those into one. This requires the punch to carry an identifier generated on the device at the moment of creation, so that two arrivals of the same event are recognisable as the same event rather than as two events with similar details.
Deduplicating on person plus time is the obvious approach and it is wrong in both directions: it merges two genuinely separate events that happened in the same minute, and it fails to merge one event whose claimed time was recomputed between retries.
Permanent refusal and temporary refusal#
This is the rule that saves a support burden, and almost nobody builds it in first.
When the server refuses a punch, the phone needs to know which kind of refusal it was:
- Try again later. Server busy, transient failure. Keep it queued.
- Stop retrying this one. Malformed, duplicate of a settled record, outside any permissible window, from a revoked device.
Without that distinction a permanently invalid punch retries for ever, draining a battery and filling logs. We marked our four payload refusals explicitly as permanent for exactly this reason.
Location is not attendance#
A phone that reports its position in the background is a genuinely useful thing, and it is the fastest way to build something nobody trusts.
The line we drew, and would draw again: background location is a signal, not an activity.
Concretely, that means a background position sample never counts toward the presence window, never counts as an activity, never decides which site somebody was at for the day, and never on its own grants access to anything. It is recorded, and it is recorded only while the person is punched in — with the server enforcing that, not the app.
The reason is not squeamishness. It is that an attendance figure derived partly from deliberate punches and partly from ambient location is a figure nobody can explain. When somebody disputes a day, you need to be able to say what was recorded and by whom. Mixing an act with an observation destroys that distinction permanently — and the dispute is rarely academic, because these are the numbers a labour contractor's bill is built from, as paying a labour contractor sets out.
Two further details that turned out to matter. A device battery level and the kind of position fix are worth storing alongside the sample, because they explain a lot of otherwise inexplicable gaps. And a step count from a phone should be nullable, never zero-by-default — no phone reported and the person did not move are different facts and collapsing them loses the ability to tell a dead battery from a stationary day.
One law, two doors#
The strongest structural rule we applied: an offline punch from the phone goes through the same function as a punch from the web.
Not a similar function. The same one, with a different authentication route into it. Every rule about permissible windows, about sites, about duplicates, about what a punch may and may not do, lives in one place and applies to both.
The temptation to write a separate, simpler path for the device is strong, because the device's needs are different and the shared function has awkward edges. It is the same temptation that produces two implementations of any rule, and the result is always the same: they agree at first and drift, and the drift is silent because each is internally consistent. That is the failure described in a fact that had no owner, arriving from a different direction.
The kill switch has to cover both doors#
One more thing, learned by asking the right question at the right moment.
If there is an emergency lock that stops the application, it must stop the device door too. A panic lock that stopped every screen but let phones keep posting locations would not have stopped anything — it would have produced a system that looked locked and was still collecting.
Any capability reachable by a second route needs every control that applies to the first, and it is worth walking every door in a system and asking that question explicitly rather than assuming.
The short version#
Offline attendance is not a networking problem. It is a trust problem.
Authenticate the device rather than the message. Record claimed time and received time separately. Refuse a punch with no position rather than inventing one. Distinguish permanent refusals from temporary ones. Keep background location as a signal that never becomes an activity.
And send both routes through the same law, because two implementations of one rule is two answers waiting to happen.
The related question of how site staff report anything at all when the network is poor is covered in WhatsApp is the interface.