Documentation
LightCaptcha v1.0.0 — A free product by vCaptcha
Installation
LightCaptcha is distributed as a single ZIP file. Download it, unzip it into your webroot, and you're ready to go.
# Download and unzip into your project root unzip lightcaptcha-v1.0.0.zip # Your project will look like this: your-website/ ├── lightcaptcha/ │ ├── js/ │ │ └── lightcaptcha.js │ ├── images/ │ │ └── puzzle_01_sunset.jpg (+ 9 more) │ └── server/ │ ├── lightcaptcha.php │ ├── lightcaptcha-verify.php │ ├── lightcaptcha.py │ └── lightcaptcha.js
Quick start
Three steps to integrate LightCaptcha into any form.
Step 1 — Set your secret key
Open lightcaptcha/server/lightcaptcha-verify.php and set your secret key at the top of the file:
define('LIGHTCAPTCHA_SECRET', 'your-long-random-key-here');
openssl rand -hex 32 on any Linux or macOS terminal. Use the same key in both the verify endpoint and your form handler.Step 2 — Add the widget to your form
<form method="POST" action="/submit.php"> <!-- your form fields --> <div data-lightcaptcha data-lc-verify="/lightcaptcha/server/lightcaptcha-verify.php" data-lc-images="/lightcaptcha/images"> </div> <button type="submit">Submit</button> </form> <script src="/lightcaptcha/js/lightcaptcha.js"></script>
Step 3 — Verify on form submit
In your form handler, verify the token before processing anything:
define('LIGHTCAPTCHA_SECRET', 'your-long-random-key-here'); require_once 'lightcaptcha/server/lightcaptcha.php'; if (!LightCaptcha::check()) { http_response_code(400); die('Captcha verification failed.'); } // Safe to process the form
Configuration
LightCaptcha uses PHP constants for server-side configuration. Define them before including the library:
// Required define('LIGHTCAPTCHA_SECRET', 'your-key'); // Optional — defaults shown define('LIGHTCAPTCHA_TTL', 600); // token lifetime in seconds define('LIGHTCAPTCHA_FIELD', 'lc_token'); // POST field name
Generating a secret key
Use any of these to generate a cryptographically secure key:
# Linux / macOS terminal openssl rand -hex 32 # PHP bin2hex(random_bytes(32)); # Python import secrets; secrets.token_hex(32) # Node.js require('crypto').randomBytes(32).toString('hex')
JavaScript widget
The widget is a single vanilla JS file with no dependencies. Include it once on any page that has a LightCaptcha element, and it auto-initializes.
Data attributes
The simplest integration — configure everything via HTML attributes:
<div data-lightcaptcha data-lc-verify="/lightcaptcha-verify.php" data-lc-images="/lightcaptcha/images" data-lc-theme="auto" data-lc-threshold="strict" data-lc-token="lc_token"> </div>
| Attribute | Values | Default | Description |
|---|---|---|---|
| data-lc-verify | URL path | ./lightcaptcha-verify.php | Endpoint the widget POSTs behavioral data to |
| data-lc-images | URL path | ./lightcaptcha/images | Path to puzzle image folder |
| data-lc-theme | auto / light / dark | auto | Widget color theme |
| data-lc-threshold | strict / relaxed | strict | Behavioral scoring sensitivity |
| data-lc-token | string | lc_token | Name of the hidden token field |
Manual initialization
For more control, initialize the widget in JavaScript:
const captcha = new LightCaptcha('#my-captcha', { verifyEndpoint: '/lightcaptcha-verify.php', imagesPath: '/lightcaptcha/images', theme: 'auto', // 'auto' | 'light' | 'dark' threshold: 'strict', // 'strict' | 'relaxed' tokenField: 'lc_token', labels: { instruction: 'Drag the puzzle piece into position', verifying: 'Verifying\u2026', success: 'Verified \u2713', retry: 'Try again' } });
Widget API
| Method | Returns | Description |
|---|---|---|
| getToken() | string | null | Returns the current signed token, or null if not yet verified |
| reset() | void | Resets the widget to its initial state (e.g. after a failed submission) |
Custom images
Point data-lc-images (or imagesPath) at any folder of JPEG or PNG images. The widget randomly picks from the 10 bundled filenames by default. To use your own images, serve them with the same filenames, or extend the _getImages() method in lightcaptcha.js.
Theming the widget
The widget supports three themes set via data-lc-theme or the theme config option:
- auto — follows the system preference (
prefers-color-scheme) - light — always light
- dark — always dark
To change the widget's colors, edit the CSS variables at the top of lightcaptcha.js in the _injectStyles method.
PHP library
Include lightcaptcha.php after defining your constants. The library exposes a single static class.
LightCaptcha::check()
Reads $_POST['lc_token'] automatically and returns true if valid.
if (!LightCaptcha::check()) { http_response_code(400); die('Captcha check failed.'); }
LightCaptcha::verify($token)
Verify a token string explicitly.
$token = $_POST['lc_token'] ?? ''; $ok = LightCaptcha::verify($token); // bool
LightCaptcha::issue()
Issue a signed token. Used inside lightcaptcha-verify.php — you don't normally call this directly.
$token = LightCaptcha::issue(); echo json_encode(['token' => $token]);
Python library
The Python library works with any framework — Flask, Django, FastAPI, or plain WSGI.
Setup
from lightcaptcha import LightCaptcha lc = LightCaptcha( secret='your-secret-key', ttl=600, # optional, default 600 field='lc_token' # optional, default 'lc_token' )
Flask example
@app.route('/lightcaptcha-verify', methods=['POST']) def lc_verify(): data = request.get_json() elapsed = int(data.get('elapsed', 0)) if elapsed < 500: return jsonify({'token': None}) return jsonify({'token': lc.issue()}) @app.route('/submit', methods=['POST']) def submit(): if not lc.verify(request.form.get('lc_token')): return 'Captcha failed', 400
Node.js library
Uses Node's built-in crypto module — no npm dependencies required.
Setup
const LightCaptcha = require('./lightcaptcha/server/lightcaptcha'); const lc = new LightCaptcha({ secret: process.env.LC_SECRET, // required ttl: 600, // optional field: 'lc_token' // optional });
Express example
app.post('/lightcaptcha-verify', (req, res) => { const { elapsed = 0 } = req.body; if (elapsed < 500) return res.json({ token: null }); res.json({ token: lc.issue() }); }); app.post('/submit', (req, res) => { if (!lc.verify(req.body.lc_token)) return res.status(400).json({ error: 'Captcha failed' }); // process form... });
Behavioral threshold
| Setting | What it requires to pass silently |
|---|---|
| strict | Mouse or touch movement with variance, at least 2 seconds on page, no linearly robotic movement patterns |
| relaxed | Any interaction (mouse, touch, or keyboard), at least 500ms on page |
Start with strict. Switch to relaxed only if you see false positives from real users — for example, on touch-only devices where users land and immediately tap a field.
Token lifetime
Tokens expire after 10 minutes by default. Adjust with:
// PHP define('LIGHTCAPTCHA_TTL', 300); // 5 minutes # Python lc = LightCaptcha(secret='...', ttl=300) // Node.js const lc = new LightCaptcha({ secret: '...', ttl: 300 });
Security notes
- Keep your secret key private. Never commit it to version control. Use an environment variable or a config file outside the webroot.
- Always validate server-side. Never trust the client alone. The token must be verified on every form submission.
- Tokens are TTL-based, not single-use. For high-value targets (login, password reset), combine LightCaptcha with rate limiting.
- Constant-time comparison. All three libraries use constant-time HMAC comparison to prevent timing attacks.
- LightCaptcha is designed to stop casual bots and spam. For hardened security requirements, layer it with additional server-side measures.
Changelog
v1.0.0 — Initial release
- Behavioral fingerprinting with strict/relaxed threshold
- Jigsaw drag-to-place challenge
- HMAC-signed token — PHP, Python, and Node.js libraries
- 10 bundled puzzle images, custom image support
- Light, dark, and auto themes
- Demo page