Put a QR code on the paperwork so the document can't lie
In a paper-heavy market, a printed invoice is something people pay against, which makes it an attack surface. So make the paper verify itself.
In a lot of the markets I build for, the source of truth isn’t your database. It’s a piece of paper. A printed invoice, a delivery note, a receipt, the thing someone physically hands over and someone else pays against. And the moment money moves on the strength of a printout, that printout is an attack surface.
Anyone with a word processor can produce a document that looks exactly like yours with different numbers on it. Your real system never sees the forgery. The first you hear of it is a dispute. So the question I kept circling was: how do you make a piece of paper prove it came from you, without a special scanner, an app install, or asking anyone to just trust the letterhead?
The paper has to point back at the truth
The answer turned out to be small. Stamp every generated document with a branded QR code that points at a public verify page on your own domain, and print a short signature next to the human-readable figures. Scan the code, land on /verify, and see the real values straight from the system. If the paper says one number and the live page says another, the paper is lying.
The part people overcomplicate is that signature. You do not need a blockchain. You do not even need a new database column. The fields that matter (who, what, how much, when) already live in your system. So you take those exact fields, run an HMAC over them with a server-side secret, and that is your signature. At /verify you recompute the same HMAC from the stored record and compare. Match means authentic. No match means tampered.
Trust shouldn’t depend on letterhead. A document should be able to prove itself with nothing but a phone camera and your own domain.
Why recompute instead of store
It’s tempting to generate the signature once and save it on the row. Don’t bother, and don’t even want to. If you recompute it from the source fields every time someone verifies, the check is always against the current truth. There’s no second copy to drift, nothing to keep in sync, nothing extra to migrate. The signature isn’t data you keep. It’s a function of data you already have.
It also fails safe. Change an amount through some legitimate path and the old printed QR simply stops verifying, which is exactly what you want. The paper is frozen in time, the system is not, and the mismatch surfaces itself.
Make it boring and visible
A few things that made it actually work in the field:
- Put it where the eye lands. The QR and signature go next to the totals, not in a footer nobody reads.
- Verify on your own domain. The page people land on is part of the trust. A link through a random shortener undoes the whole point.
- Keep the secret server-side and rotatable. The whole scheme rests on that one secret. Treat it like the key it is.
None of this is clever cryptography. It’s the most ordinary HMAC you’ll ever write, pointed at the one place trust was actually leaking: the gap between a printout and your database. In a low-trust, paper-heavy market, closing that gap with a square of dots was worth more than most features I shipped that quarter.