Building my own contact form spam check

While updating the contact form, I wanted a small barrier that fit the site instead of dropping in a large third-party widget. I could have used reCAPTCHA and moved on quickly, and for some products that would be the right call. For this site, part of the fun was treating the form as a small design problem: how far could I get with a focused, understandable solution that I owned end to end?

The weak spot was obvious. If the text is printed directly into the HTML, a basic script can collect it almost as well as a person can. So the server now creates a simple arithmetic check, breaks the characters into shuffled fragments, and stores those fragments in the markup in a lightly encoded form. The browser puts the text back together for visitors, while the raw page source is less useful to a drive-by bot.

The important part still happens on the server. The hidden token includes the issue time, a random nonce, and a keyed digest of the expected answer. When the form is submitted, the server checks the signature, rejects stale tokens, and compares the submitted value with the signed digest. That means the page can display the check without trusting the browser to decide whether it was solved.

This is not meant to be a universal anti-spam system. A determined attacker could inspect the JavaScript and automate around it. But for a personal contact form, the goal is more practical: avoid the simplest automated submissions, keep the experience quiet for real visitors, and learn from building the moving parts myself instead of immediately handing the job to another service.