Fast. Private. Human. A behaviour-based CAPTCHA widget with no tracking, no cookies, and no third-party calls. One script tag to install.
This is the open-source frontend for Szymdows CAPTCHA - a privacy-friendly alternative to reCAPTCHA and hCaptcha.
Instead of making users squint at blurry traffic lights, Szymdows CAPTCHA asks them to drag a shape into a target zone or tap tiles in a sequence. It watches how they move - the speed, the curve, the hesitation - and uses that to decide if it's a human. No accounts needed. No data sold. No Google.
This repo contains:
szymdows-captcha.js- the widget script that goes on websitesassets/icon.png- the widget icon
The documentation page:
- https://szymdows.com/captcha - the documentation page
The scoring backend is proprietary and not in this repo. This is intentional - keeping the detection logic private makes it harder for bots to reverse-engineer and defeat. The frontend you see here is everything a website owner needs.
Paste this into the <head> of any HTML page:
<script src="https://cdn.szymdows.com/szymdows-captcha.js"></script>Then put a <div> where you want the CAPTCHA to appear:
<div id="my-captcha"></div>Then show it with a couple of lines of JavaScript:
<script>
SzymdowsCaptcha.render('my-captcha', {
onSuccess: function(token) {
// User passed! Enable your submit button, send the token to your server, etc.
document.getElementById('submit-btn').disabled = false;
},
});
</script>That's it. No npm. No build tools. No configuration.
<div id="my-captcha"></div>
<button id="submit-btn" disabled>Submit</button>
<script>
SzymdowsCaptcha.render('my-captcha', {
onSuccess: function(token) {
// token is a signed string - send it to your server
document.getElementById('submit-btn').disabled = false;
},
onFail: function() {
// Score too low - widget retries automatically after 2.5s
document.getElementById('submit-btn').disabled = true;
},
onExpire: function() {
// Auto-resets after 5 minutes so old tokens can't be reused
document.getElementById('submit-btn').disabled = true;
},
theme: 'auto', // 'light' | 'dark' | 'auto' (follows OS setting)
});
</script><div id="my-captcha" style="display:none"></div>
<button id="submit-btn">Submit</button>
<script>
document.getElementById('submit-btn').addEventListener('click', function() {
// Already verified? Submit straight away
if (SzymdowsCaptcha.isVerified('my-captcha')) {
sendForm(SzymdowsCaptcha.getToken('my-captcha'));
return;
}
// Show and load the CAPTCHA
document.getElementById('my-captcha').style.display = 'block';
SzymdowsCaptcha.show('my-captcha', {
onSuccess: function(token) { sendForm(token); },
});
});
</script>| Method | Returns | Description |
|---|---|---|
SzymdowsCaptcha.render('id', opts) |
- | Show the CAPTCHA widget inside the element with that id |
SzymdowsCaptcha.show('id', opts) |
- | Same as render() - friendlier name for on-demand use |
SzymdowsCaptcha.getToken('id') |
string | null |
Get the token after the user passes (send this to your server) |
SzymdowsCaptcha.isVerified('id') |
boolean |
Check if the user has already passed |
SzymdowsCaptcha.reset('id') |
- | Clear the result and load a fresh challenge |
| Option | Default | Description |
|---|---|---|
onSuccess |
null |
Function called when the user passes. Receives the token as its first argument. |
onFail |
null |
Function called when the score is too low. Widget auto-retries after 2.5s. |
onExpire |
null |
Function called when the 5-minute auto-reset fires. |
autoReset |
true |
Auto-reset after 5 minutes so stale tokens can't be reused. |
theme |
'auto' |
'light', 'dark', or 'auto' (follows the user's OS preference). |
When a user submits your form, send the token to your server and check it:
// Node.js / Express example
app.post('/submit', async function(req, res) {
const response = await fetch('https://captcha.szymdows.com/__szc/verify-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: req.body.captchaToken }),
});
const result = await response.json();
// result = { valid: true, score: 0.87 }
// or { valid: false, error: "Token expired" }
if (!result.valid) {
return res.status(403).json({ error: 'CAPTCHA check failed' });
}
// Safe to process the form
});Always verify on your server. Never trust a token just because it arrived - always check it server-side before processing a form submission. Tokens are single-use and expire after 60 seconds.
| Browser | Minimum version |
|---|---|
| Chrome / Edge | 80+ |
| Firefox | 75+ |
| Safari | 13.1+ |
| iOS Safari | 13.4+ |
| Samsung Internet | 12+ |
Works on desktop and mobile. Touch, mouse, and keyboard (arrow keys) all supported.
- ✅ No cookies set
- ✅ No user fingerprinting
- ✅ No calls to Google, Meta, or any third party
- ✅ Movement data is processed in real-time and never stored
- ✅ No accounts or API keys required for website owners
szymdows-captcha/
│
├── szymdows-captcha.js ← The widget (this is what websites load)
└── assets/
└── icon.png ← Widget icon shown in the header
The entire widget is a single vanilla JavaScript file with no dependencies. It injects its own styles, loads its own fonts, and works in any environment - plain HTML, React, Vue, WordPress, Webflow, anything.
Contributions are very welcome! Here's how to get started - it's simpler than most projects because there's no build step.
- Bug fixes - if something doesn't work, open an issue or a PR
- Browser compatibility fixes - found a browser where it breaks? Please report it
- Accessibility improvements - making the widget work better for keyboard/screen reader users
- New challenge types - ideas for new human-verification interactions (open an issue to discuss first)
- Documentation improvements - clearer wording, more examples, better explanations
- Translations - the widget text is currently English only
-
Fork this repo - click the Fork button at the top right of this page
-
Clone your fork to your computer:
git clone https://github.com/Szymdows/szymdows-captcha.git cd szymdows-captcha -
Open https://szymdows.com/captcha in your browser - this is the documentation page and a good place to see the widget in action
-
Make your changes to
szymdows-captcha.js -
Test by opening a simple HTML file - no build step needed, just open it in a browser:
<!DOCTYPE html> <html> <head> <script src="./szymdows-captcha.js"></script> </head> <body> <div id="test-captcha"></div> <script> SzymdowsCaptcha.render('test-captcha', { onSuccess: function(token) { console.log('Token:', token); } }); </script> </body> </html>
-
Open a Pull Request - describe what you changed and why
- Keep it vanilla - no frameworks, no npm packages, no build tools. The whole point is that this loads with a single script tag.
- Keep it small - every byte added to the script is a byte every website using this has to download.
- Test on mobile too - open your test file on your phone or use browser DevTools to simulate touch.
- One thing per PR - smaller, focused pull requests are much easier to review than large ones.
- Open an issue first for big changes - if you want to add a new challenge type or change something fundamental, open an issue to discuss it before writing code. Saves everyone time.
Open an issue and include:
- What browser and version you're using
- What you expected to happen
- What actually happened
- Any error messages from the browser console (press F12 to open it)
The verification logic - how movement paths are scored, what counts as "bot-like" behaviour - is kept private on purpose. If it were open source, bot authors could read it, understand exactly what patterns get flagged, and write bots that pass every time.
The frontend (this repo) is fully open so you can see exactly what data is collected and sent, verify there's no tracking, and audit the code you're loading onto your website. That's the part that matters for trust.
MIT - free to use, modify, and distribute. See LICENSE for the full text.
The backend service at captcha.szymdows.com is proprietary.
Made by Szymdows Visit my portfolio: https://szymdows.com