hCaptcha provider

hCaptcha provider implementing captcha.Provider against the hCaptcha siteverify API.

Overview

hCaptcha is a privacy-oriented captcha service. No Google dependency and no tracking cookies. The provider supports both the free tier, which returns a binary pass or fail, and the Enterprise tier, which returns a risk score. It normalises both into Piko's standard convention where 0.0 means bot and 1.0 means human. The free tier maps to 1.0 on success and 0.0 on failure. The Enterprise tier inverts hCaptcha's native scale, where 0.0 means safe and 1.0 means threat, so one score threshold applies regardless of tier.

The provider injects the widget script, the explicit-render bootstrap, and the script, frame, and connect CSP domains through RenderRequirements. You do not write any frontend glue or render the widget yourself. Mark the action as captcha-required and Piko handles the round trip. The provider implements the same captcha.Provider interface and Config shape as Turnstile and reCAPTCHA v3, so switching providers is one bootstrap line.

The provider is a plain Go module. It carries no build tags and no CGO dependency, so it runs in interpreted dev mode (dev-i) and compiled mode alike. Token verification ships with OpenTelemetry tracing and hardened HTTP handling. It enforces a 10-second timeout, a content-type check, and a 64 KiB cap on the verification response. All public methods are safe for concurrent use.

Requirements

  • An hCaptcha account with a registered site. Site key and secret key from the hCaptcha dashboard.
  • Network egress to js.hcaptcha.com (widget script), hcaptcha.com and *.hcaptcha.com (frames and verification), and api.hcaptcha.com (server-side verification).

Configuration

import (
    "piko.sh/piko/wdk/captcha/captcha_provider_hcaptcha"
)

provider, err := captcha_provider_hcaptcha.NewProvider(captcha_provider_hcaptcha.Config{
    SiteKey:   "10000000-ffff-ffff-ffff-000000000001", // required; public site key
    SecretKey: "0x0000000000000000000000000000000000000000", // required; server-side secret
})
if err != nil {
    return err
}

To keep keys out of source, inject them through the bootstrap override instead of hardcoding them in Config:

ssr := piko.New(
    piko.WithCaptchaProvider("hcaptcha", provider),
    piko.WithDefaultCaptchaProvider("hcaptcha"),
    piko.WithCaptcha(piko.CaptchaOptions{
        SiteKey:   os.Getenv("HCAPTCHA_SITE_KEY"),
        SecretKey: os.Getenv("HCAPTCHA_SECRET_KEY"),
    }),
)

Bootstrap

ssr := piko.New(
    piko.WithCaptchaProvider("hcaptcha", provider),
    piko.WithDefaultCaptchaProvider("hcaptcha"),
)

Usage

Mark a server action as captcha-required by implementing the CaptchaProtected interface, whose CaptchaConfig method returns a *piko.CaptchaConfig. Piko verifies the token before invoking the action handler.

func (SubmitAction) CaptchaConfig() *piko.CaptchaConfig {
    return &piko.CaptchaConfig{Provider: "hcaptcha"}
}

Score threshold

The score threshold only applies to the Enterprise tier, which returns a risk score. The free tier is pass or fail, so the threshold has no effect there. The service default is 0.5. Set the service-wide threshold through the bootstrap override:

piko.WithCaptcha(piko.CaptchaOptions{ScoreThreshold: 0.7})

To override the threshold for one action, set ScoreThreshold on the returned CaptchaConfig. A value of 0 uses the service default.

func (SubmitAction) CaptchaConfig() *piko.CaptchaConfig {
    return &piko.CaptchaConfig{Provider: "hcaptcha", ScoreThreshold: 0.7}
}

See also

Other captcha providers:

Framework docs:

External: