GateCHA

Self-hosted ALTCHA CAPTCHA management with API keys, multi-site support, and statistics.
🌐 Website · 📖 Documentation · 🧩 WordPress plugin
GateCHA is an open-source alternative to ALTCHA Sentinel. It wraps the ALTCHA proof-of-work CAPTCHA protocol with a management layer: API key management, per-site configuration, replay protection, and a dashboard with statistics.
Features
- ALTCHA-compatible - Works with the official ALTCHA widget (MIT)
- API Key Management - Create keys per site with custom difficulty, TTL, and domain restrictions (multiple domains +
*.example.com wildcards)
- Replay Protection - Consumed challenges are tracked and rejected on reuse
- Statistics Dashboard - Track challenges issued, verifications (success/fail), per key, per day
- Single Binary - Vue.js dashboard embedded in the Go binary via
go:embed
- Multi-Database - SQLite by default; MySQL available in the
mysql build variant
- Docker Ready - One container, zero external dependencies (SQLite mode)
- Lightweight - ~23.8MB Docker image, ~14.2MB binary (SQLite); ~24.2MB / ~14.5MB with MySQL support
Quick Start
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 with admin / changeme.
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)
make build-mysql # with MySQL support
./gatecha
Usage
1. Create an API Key
Log in to the dashboard at http://localhost:8080, go to API Keys, and create a new 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 form fields -->
<altcha-widget
challengeurl="https://your-gatecha-host/api/v1/challenge?apiKey=gk_your_key_id"
></altcha-widget>
<button type="submit">Submit</button>
</form>
3. Verify on Your Backend
# 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'):
# Valid submission
pass
API Endpoints
Public (API Key auth via ?apiKey=gk_xxx)
| Method |
Endpoint |
Description |
GET |
/api/v1/challenge |
Generate a PoW challenge |
POST |
/api/v1/verify |
Verify a solution |
Admin (JWT auth via Authorization: Bearer)
| Method |
Endpoint |
Description |
POST |
/api/admin/login |
Authenticate |
GET |
/api/admin/keys |
List API keys |
POST |
/api/admin/keys |
Create API key |
GET/PUT/DELETE |
/api/admin/keys/:id |
Manage API key |
POST |
/api/admin/keys/:id/rotate-secret |
Rotate HMAC secret |
GET |
/api/admin/stats/overview |
Global statistics |
GET |
/api/admin/stats/keys/:id |
Per-key statistics |
GET |
/healthz |
Health check |
MySQL Support
The default build is SQLite-only for a lightweight single-binary deployment. MySQL support is compiled in only when explicitly requested.
Build locally with MySQL support:
make build-mysql
Docker image with MySQL support:
docker build --build-arg BUILD_TAGS=mysql -t gatecha:mysql .
Docker Compose with MySQL:
docker compose -f docker-compose.mysql.yml up -d
Note for contributors: When updating Go dependencies while working on MySQL support, run go mod tidy -tags mysql instead of plain go mod tidy to preserve the MySQL driver in go.mod.
Configuration
| Variable |
Default |
Description |
GATECHA_LISTEN_ADDR |
:8080 |
Listen address |
GATECHA_DB_DRIVER |
sqlite |
Database driver: sqlite always available; mysql requires the mysql build variant |
GATECHA_DB_DSN |
./data/gatecha.db |
Database DSN — file path for SQLite, connection string for MySQL (e.g. user:pass@tcp(host:3306)/dbname?parseTime=true) |
GATECHA_SECRET_KEY |
(auto-generated) |
JWT signing secret |
GATECHA_ADMIN_USERNAME |
admin |
Admin username |
GATECHA_ADMIN_PASSWORD |
(auto-generated) |
Admin password |
GATECHA_LOG_LEVEL |
info |
Log level |
GATECHA_CLEANUP_INTERVAL |
10 |
Cleanup interval (minutes) |
GATECHA_HIS_SAMPLE_RETENTION_DAYS |
30 |
Retention (days) for opted-in raw HIS calibration samples |
GATECHA_CORS_ALLOW_ALL |
false |
Allow CORS from any origin |
GATECHA_TRUST_PROXY |
false |
Trust X-Forwarded-For/X-Real-IP for the client IP. Set to true when behind a reverse proxy (see note below) |
GATECHA_ENABLE_HSTS |
false |
Send the Strict-Transport-Security header (enable only when always served over HTTPS) |
GATECHA_MAX_BODY_BYTES |
1048576 |
Maximum accepted request body size, in bytes |
GATECHA_RATE_LIMIT_ENABLED |
true |
Enable per-IP rate limiting |
GATECHA_RATE_LIMIT_LOGIN |
5 |
Admin login requests per minute, per IP |
GATECHA_RATE_LIMIT_API |
60 |
Public API (/api/v1/*) requests per minute, per IP |
⚠️ Behind a reverse proxy, set GATECHA_TRUST_PROXY=true.
Per-IP rate limiting keys off the connecting IP. With GATECHA_TRUST_PROXY=false
behind a proxy, that IP is the proxy itself, so every visitor shares a single
rate-limit bucket. It exhausts almost immediately, the public ALTCHA challenge
endpoint starts returning 429s, and the login captcha breaks with
"Expected application/json, received text/html". Enabling TRUST_PROXY makes
the limiter use each visitor's real IP. Only enable it behind a trusted proxy
that sets X-Forwarded-For/X-Real-IP, otherwise clients can spoof their IP.
License
MIT - see LICENSE.