Skip to content

ALTCHA setup

ALTCHA is the choice when you want bot protection but don’t want to depend on (or send visitors to) a third party like Cloudflare. The browser solves a small computational puzzle on submit; your server issues and verifies it using a secret you control.

ALTCHA challenges are signed with a secret that never leaves your server. Generate a strong random one:

Terminal window
openssl rand -hex 32
CAPTCHA_PROVIDER=altcha
ALTCHA_HMAC_KEY=your-generated-secret
# Optional — proof-of-work difficulty (max number the browser brute-forces). Higher = harder for
# bots but slightly slower for legitimate visitors. Default is fine for most instances.
ALTCHA_MAX_NUMBER=100000

CAPTCHA_PROVIDER=altcha activates both the client-side widget on the booking form and server-side verification. calit fails fast at startup if CAPTCHA_PROVIDER=altcha is set without ALTCHA_HMAC_KEY, so a misconfiguration can never silently accept forged solutions.

The widget fetches its challenge from GET /altcha/challenge on your instance (public, unauthenticated, cheap). No special proxy configuration is needed beyond forwarding normal traffic — if you already proxy the app, the endpoint works. The widget script is served by calit itself (no CDN), so it also works on fully air-gapped deployments.

ALTCHA challenges the public booking form on submission. It is layered with two abuse controls that are always active:

  • A honeypot field to catch naive bots.
  • A per-email daily cap (PER_EMAIL_DAILY_CAP, default 10) limiting how many bookings a single email address can submit per day.

The widget’s own strings (“I’m not a robot”, “Verifying…”) are localised automatically from the page language — English, German, and Hebrew (right-to-left) are all covered, matching calit’s own UI languages. No extra configuration.

  • Enabling any CAPTCHA provider makes the booking form require JavaScript (the proof-of-work runs in the browser). With CAPTCHA_PROVIDER=none (default) the form works without JavaScript.
  • The widget JavaScript is bundled with calit and served from your instance — nothing is fetched from a CDN at runtime.