As a maintainer of several websites with contact forms, I’ve fought my battles with spam bots. This can easily be solved using a CAPTCHA or reCAPTCHA, but I’d rather use those options as a last resort. Honestly, CAPTCHA solutions are a personal annoyance.

This writeup outlines the time trap technique that has been very effective for regular use. By regular use, I mean the website you’re protecting isn’t big enough for a spammer to target specifically.

1. The concept

The basic concept of a time trap is very simple:

  1. When generating the form, the server adds a hidden form field containing a timestamp
  2. A spam bot fills the form automatically (usually very fast)
  3. The form is submitted to the server
  4. The server compares the timestamp in the form field against the current time
  5. If the time difference is beyond typing speed, it’s probably a spambot

To reduce the chance of this technique being detected, two additional measures could be taken:

  • Encode the timestamp so that it’s not easily recognised as a timestamp
  • Use a sensible, confusing name for the time trap field (e.g. ‘token’ instead of ‘timetrap’,’spam_protection’, …)

2. Example implementation

The concept above can be implemented in numerous ways, below is an example in Symfony to get started with:

class Contactform {

    // configuration: some random number to use for encoding the time value
    private $salt = 234234;
    // configuration: minimum time needed to complete the form
    private $minimumTime = 3; // seconds

    private function createTimetrap() {
        // encode the time value
        return base64_encode(json_encode(time()*$this->salt));

    private function checkTimetrap($value) {
       // decode the time value
       $timetrap = json_decode(base64_decode($value));
       // check if the time is above the minimum time needed to complete the form
       if (is_numeric($timetrap) && time() - ($timetrap/$this->salt) > $this->minimumTime) {
            // it's a human
            return true;
       } else {
            // it's a bot
            return false;

For the minimum time, just experiment using a stopwatch and pick a reasonable time for a human to fill in the form.

3. Back-up plan

Let’s assume a super fast human fills in your form. You want to give him/her some contact option in this case:

if(!$this->checkTimetrap($data['time'])) {
    die("Aborting due to spam detection. Please use our chat widget to get in touch.");

And that’s it!