Why the static QR code never worked
The first "QR attendance" products shipped with a single static code per class. The instructor printed it, taped it to the whiteboard, and trusted students to scan at the start of class. It fell apart the same week it launched: a student in the back row took a photo, sent it to five absent friends on Slack, and all five were "present" without leaving their dorms.
Any attendance system that relies on a code students can screenshot and forward is just a buddy-check system with extra steps.
What "rotating" actually means
In Autotend, the QR code on the projector regenerates every few seconds. If you take a screenshot and send it to a friend, by the time they open the image, the code on the screen has already rolled to a new value. The old one is dead.
Under the hood, each session has a secret key. The code encodes a signed tuple of:
(session_id, rotation_counter, timestamp)
The server accepts a scan only if the signature is valid and the rotation counter matches the one it's currently emitting (plus a small grace window to account for network lag). Screenshots from ten seconds ago fail the check and return a friendly "this code has expired."
The math: how often should it rotate?
Short intervals mean less cheating but worse UX for a student with a slow phone or a spotty signal. Too long and the buddy-check pattern sneaks back in. We spent a week profiling real scans and landed on a conservative default.
| Rotation window | Screenshot sharing | UX impact |
|---|---|---|
| 30s | Noisy, still viable | Invisible |
| 15s | Very hard | Invisible |
| 8s | Effectively blocked | One-in-fifty retry |
| 4s | Blocked | One-in-ten retry |
| 2s | Blocked | Users rescan often, annoying |
We default to eight seconds. Most real phones complete the scan + network round trip in under four. The eight-second window gives a full two-second buffer on top, so network hiccups don't force a retry — but anyone trying to relay the image has to do it in under eight seconds across Slack, iMessage, or whatever. In practice that collapses the cheating surface to essentially zero.
The grace window
A literal read of "the current counter" breaks on the exact moment a rotation happens. If your phone sends the scan at T = 7.9 seconds but the server ticks over at T = 8.0 before it arrives, the code is "expired" by 0.1 seconds. That's a bad user experience for a phantom problem.
So the server accepts the current counter or the previous one, within a ~1.5s window. That's the full grace. Anything older is hard rejected. We've watched a lot of traffic at this point and the grace window catches the marginal cases without creating a new cheating path.
What rotation doesn't fix
Rotation alone doesn't stop every attack. Some students will ask a friend to screen-share the projector over FaceTime, and scan in real time. For that, we bolted on two more checks:
- Optional GPS verification. Scans outside the class's geofence are rejected. Privacy-respecting: we check once at scan time and only store the result, not the coordinate.
- Device fingerprinting. One device can't register two check-ins for the same session without the admin explicitly allowing it.
Neither is bulletproof alone. Layered, they make attendance cheating about as much work as showing up — at which point, why not just show up.
The takeaway
Rotating QR codes aren't a silver bullet, and we don't pretend they are. What they are is a cheap, fast way to close the laziest 95% of the attendance-fraud surface, which is how it gets exploited most of the time. The math above is the boring reason Autotend feels harder to game than the alternatives.
If you want to try it, start a free class and point your phone at the projector.
