Documentation

Everything you need to install, configure, integrate and secure your self-hosted GateCHA server — the open-source ALTCHA CAPTCHA management layer.

Introduction

GateCHA is a self-hosted, open-source (MIT) alternative to ALTCHA Sentinel. It wraps the ALTCHA proof-of-work CAPTCHA protocol with API-key management, multi-site support, replay protection, adaptive difficulty, per-key rate limiting, a privacy-first statistics dashboard, and a DIY Human Interaction Signature — all in a single Docker container.

  • ALTCHA-compatible — works with the official ALTCHA widget; no proprietary client.
  • Self-contained — one container, embedded SQLite (or MySQL), no external services.
  • Privacy-first — no client IP is ever stored or logged; only aggregated, anonymous counters.

Installation

Docker Compose (recommended)

mkdir -p /opt/docker/GateCHA && cd /opt/docker/GateCHA
wget https://raw.githubusercontent.com/Upellift99/GateCHA/refs/heads/main/docker-compose.yml
docker compose up -d

Open http://localhost:8080 and log in. If you did not set GATECHA_ADMIN_PASSWORD, a random one is printed to the container logs on first boot.

Docker Run

docker run -d -p 8080:8080 \
  -v gatecha_data:/app/data \
  -e GATECHA_ADMIN_PASSWORD=your-password \
  ghcr.io/upellift99/gatecha:latest

From source

# Prerequisites: Go 1.26+, Node.js 20+
git clone https://github.com/Upellift99/GateCHA.git
cd GateCHA
make build        # SQLite only (default)
./gatecha

Configuration

GateCHA is configured entirely through environment variables. All values are optional; sensible defaults apply.

VariableDefaultDescription
GATECHA_LISTEN_ADDR:8080Listen address.
GATECHA_DB_DRIVERsqlitesqlite (always available) or mysql (mysql build variant).
GATECHA_DB_DSN./data/gatecha.dbSQLite file path, or MySQL connection string.
GATECHA_SECRET_KEYautoJWT signing secret. Set it to keep sessions valid across restarts.
GATECHA_ADMIN_USERNAMEadminAdmin username.
GATECHA_ADMIN_PASSWORDautoAdmin password (printed once to stderr if generated).
GATECHA_LOG_LEVELinfodebug / info / warn / error.
GATECHA_CLEANUP_INTERVAL10Expired-challenge cleanup interval, in minutes.
GATECHA_HIS_SAMPLE_RETENTION_DAYS30Retention for opted-in raw HIS calibration samples.
GATECHA_CORS_ALLOW_ALLfalseAllow CORS from any origin (default: per-key domain check).
GATECHA_TRUST_PROXYfalseTrust X-Forwarded-For / X-Real-IP. Set to true behind a reverse proxy (see note).
GATECHA_ENABLE_HSTSfalseSend Strict-Transport-Security (HTTPS-only deployments).
GATECHA_MAX_BODY_BYTES1048576Maximum accepted request body size, in bytes.
GATECHA_RATE_LIMIT_ENABLEDtrueEnable per-IP rate limiting.
GATECHA_RATE_LIMIT_LOGIN5Admin login requests per minute, per IP.
GATECHA_RATE_LIMIT_API60Public API requests per minute, per IP.
Behind a reverse proxy, set GATECHA_TRUST_PROXY=true. Otherwise per-IP rate limiting keys off the proxy's IP — every visitor shares one bucket, which exhausts immediately and breaks the public challenge endpoint (and the login captcha). Only enable it behind a trusted proxy that sets the forwarding headers.

Widget integration

Add the official ALTCHA widget (v3) to your form and point its challenge attribute at your GateCHA challenge endpoint, authenticated with the site's API key.

<script async defer src="https://cdn.jsdelivr.net/npm/altcha/dist/altcha.min.js" type="module"></script>

<form action="/your-endpoint" method="POST">
  <!-- your fields -->
  <altcha-widget
    challenge="https://your-gatecha-host/api/v1/challenge?apiKey=gk_your_key_id"
  ></altcha-widget>
  <button type="submit">Submit</button>
</form>

Verify on your backend

The widget posts an altcha payload with your form. Forward it to GateCHA's verify endpoint and check ok before trusting the submission. The challenge is single-use — replays are rejected.

# Example: Python
import requests

altcha_payload = request.form.get('altcha')
resp = requests.post(
    'https://your-gatecha-host/api/v1/verify?apiKey=gk_your_key_id',
    json={'payload': altcha_payload},
)
if resp.json().get('ok'):
    pass  # valid submission
Use the v3 challenge attribute, not the v2 challengeurl. The exact integration snippet for each key is shown on its detail page in the dashboard.

API reference

Public API — authenticated with ?apiKey=gk_…

GET /api/v1/challenge Generate a proof-of-work challenge.
POST /api/v1/verify Verify a solution — body { "payload": "…", "his_signals"?: {…} }.

Admin API — JWT via Authorization: Bearer

POST /api/admin/login Authenticate, returns a JWT.
GET /api/admin/version Build version (authenticated).
GET /api/admin/keys List API keys.
POST /api/admin/keys Create an API key.
GET /api/admin/keys/{id} Get / PUT update / DEL delete a key.
POST /api/admin/keys/{id}/rotate-secret Rotate the HMAC secret.
GET /api/admin/stats/overview Global stats incl. per-country breakdown.
GET /api/admin/stats/keys/{id} Per-key stats.
GET /api/admin/his/calibration HIS score distribution (calibration).
GET /healthz Health check (unauthenticated).

Security levers

Each API key carries its own protection settings, all editable from the dashboard. PoW alone deters casual automation; combine these for layered defense.

Per-key difficulty

max_number sets the proof-of-work cost (higher = harder = slower for the client). Pick per site based on abuse pressure vs. UX. expire_seconds bounds challenge validity; algorithm selects SHA-256 (default) or SHA-512.

Domain restrictions

A key can be locked to one or more origins. List one domain per line (commas also work); requests whose Origin/Referer host matches any entry are allowed. Use *.example.com to allow every subdomain and the bare example.com. Leave the field empty to allow any origin.

Per-key rate limiting

rate_limit_per_min caps challenge + verify calls for a key across all clients (0 = unlimited). This is in addition to the global per-IP limiter (GATECHA_RATE_LIMIT_API).

Adaptive difficulty

When enabled, GateCHA raises the PoW cost above the key's base for any source (by IP) requesting challenges at an abusive rate — escalating an attacker without penalizing normal visitors. The configured difficulty stays the floor; it never blocks.

Human Interaction Signature (HIS)

An open-source take on the concept behind ALTCHA Sentinel's proprietary HIS. The client collects privacy-preserving aggregates of interaction behaviour (counts, pointer distance, durations, timing variance — never coordinates, timestamps or key contents) and the server scores the automation probability.

  • Monitor mode (default) records and counts bot-suspected samples but never blocks.
  • Sampling (opt-in per key) stores the raw aggregates so you can calibrate a threshold on real traffic, with a configurable retention window. The dashboard shows the score distribution against the suspect threshold.
Enable HIS sampling on a busy key for a few days, then read the calibration histogram before considering any enforcement — calibrating on your own traffic avoids false positives.

Privacy & data

GateCHA is privacy-first by construction. It is designed so you can run a CAPTCHA without becoming a tracking vector.

  • No IP is ever stored or logged. The client IP is used only in memory for rate limiting and adaptive difficulty.
  • Country-only geolocation. The traffic-by-country panel resolves the IP to a country code at request time (via an embedded, offline database) and immediately discards the address — only the aggregated country counter is persisted.
  • HIS stores aggregates only — counts, distances, durations and timing variance; never coordinates, timestamps or what was typed.
  • Self-contained. No third-party CDN for fonts or scripts in the admin UI; nothing phones home.

Resources