Product updates, guides, and more

Stay up to date with the news and learn how to get the most out of the platform.

How I Stopped Spam Signups with a Custom Honeypot Captcha

How I Stopped Spam Signups with a Custom Honeypot Captcha

Sep 20, 2025 Security ๐Ÿ‘๏ธ 1 reads

Last updated: 2026-02-04

Oops!

We all make mistakes. Mine? I deployed a new version of the site and forgot to protect the /register route. Then I went AFK. By the time I checked back, over 250 fake accounts had been created by bots.

Related reading: product features, Statuspage alternatives, and the incident comms primer What Is a Status Page?.

The good news? I had already been working on a small invisible captcha library - a honeypot-based solution thatโ€™s effortless for users but brutal on bots. I finalized it, integrated it, and released it as gocaptcha.

What I did

  • I added a simple hidden input field to the registration form. Itโ€™s named something like nickname and styled so that users never see it.
  • On form submission, the server checks whether the field is filled. If it is, the request is likely from a bot and is rejected.
  • I wrapped the logic in a clean Gin middleware and released it as gocaptcha - so any Gin-based app can plug it in with minimal effort.

Why honeypots work ๐Ÿฏ

Spam bots typically fill every field they find in a form. By introducing a field that legitimate users will never touch - but bots will - we can confidently detect automated signups with virtually no user friction.

Itโ€™s simple, elegant, and surprisingly effective.

The results

After deploying the honeypot middleware in my Gin app, the effect was immediate.

Spam signups dropped from hundreds to zero - instantly.

Hehe

๐Ÿ’ก Hereโ€™s a glimpse from the admin dashboard, showing the captcha logs and the now-empty /register route:

Captcha logs

How you can use it

1. Add the hidden input to your form

<form action="/register" method="post">
  <input type="text" name="nickname" style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;" autocomplete="off">
  <!-- visible fields here -->
</form>

2. Validate on the server using gocaptcha

import (
  "github.com/gin-gonic/gin"
  "github.com/dragstor/gocaptcha"
)

r := gin.Default()

cap := gocaptcha.New(gocaptcha.Config{
    ShowBadge:      true,                     // show small lock badge (optional)
    BadgeMessage:   "Protected by GoCaptcha", // badge text
    RateLimitTTL:   time.Minute,              // per-IP window
    RateLimitMax:   10,                       // max requests/window
    EnableStorage:  true,                     // enable SQLite logs + seeding
    DBPath:         "./captcha.db",           // defaults to ./captcha.db if empty
    BlockThreshold: -5,                       // block when score <= threshold
    // Bypass OAuth callbacks:
    SkipPaths:         []string{"/auth/", "/oauth2/"},
    TrustProxyHeaders: true, // respect X-Forwarded-For when behind a proxy (I use Caddy reverse proxy)
})

r.POST("/register", func(c *gin.Context) {
    if cap.CheckRequest(c.Request) {
		// redirect to a login page if bot detected, making them feel like they succeeded
		c.Redirect(http.StatusSeeOther, mainDomain+"/user/login")
		return
	}
  // If middleware passes, continue with registration logic
})

โš ๏ธ A few notes

  • Honeypots are simple and highly effective, but theyโ€™re not bulletproof. For maximum protection, combine them with rate limiting, IP filters, and email verification.
  • Choose a believable name for the hidden field - something like "nickname" or "middle_name". Avoid anything obviously fake.
  • Donโ€™t rely on JavaScript alone to hide the field. Use CSS, so non-JS bots still fall into the trap.

Want to try it yourself?

Grab gocaptcha from GitHub: https://github.com/dragstor/gocaptcha

Itโ€™s lightweight, Gin-native, and it completely solved my bot problem.


Tags: captcha, spam, honeypot, gin

Want to see it in action? Head to the live demo:

Protected by gocaptcha

Author avatar
Nikola Stojkoviฤ‡
Published Sep 20, 2025
Related Posts

No related posts were found

Share & Subscribe