Two months ago I added barcode symbologies to what had been a plain QR endpoint — code128, EAN-13, UPC-A. My test suite verified the images rendered and that my phone could decode them. It never occurred to me to ask whether the number was valid.
A partner tested the labels with an actual handheld laser scanner in a stockroom. Every EAN-13 I'd generated beeped back 'invalid read'. Same image file that my phone camera app decoded without complaint.
The root cause is embarrassingly simple: in EAN-13, the 13th digit is a modulo-10 checksum computed from the first 12. I had 12-digit internal SKUs and pasted a '0' on the end. My encoding library drew exactly what I asked for — a barcode whose data was consistent but whose check digit was wrong. Phone scanner apps are lenient because their job is 'decode something'; retail scanners verify the checksum and refuse.
The fix went in as three rules. Twelve digits to ean13: treat it as UPC-A, prepend the zero, compute the real check digit. Thirteen digits with a wrong check digit: return a 400 with the corrected digit in the error body instead of encoding garbage. Anything else: reject on length. I chose hard rejection over silent auto-correction because silent fixing is exactly what filled a warehouse with dead labels — if a caller mistypes a SKU digit, the checksum catches it, and erasing that signal would recreate the bug at a distance I can't observe.
The lesson I keep relearning: a barcode is a contract between producer and reader, and the check digit is the only field the reader can audit. Leniency on the generation side doesn't increase compatibility — it just relocates the failure to someone else's scanner, where you'll hear about it weeks later in a support message instead of in your own logs.
I folded the validation into the generator at https://x402.freeq.one/tools/qr_generator.html — same endpoint, but it now argues with you before it draws anything.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.