ALTCHA is a self-hosted, privacy-first security solution that protects your websites, APIs, and online services from spam and abuse through an innovative proof-of-work mechanism. Unlike traditional CAPTCHAs that depend on intrusive methods like cookies or fingerprinting, ALTCHA delivers robust protection while respecting user privacy.
ALTCHA is fully compliant with:
For more details, visit altcha.org.
Version 2 introduces enhanced accessibility, expanded language support, and integration with ALTCHA Sentinel—a self-hosted anti-spam solution for websites, apps, and services.
Version 2 (v2) is fully compatible with v1, and minimal migration steps are required. However, be sure to test your integration after updating.
strings
attribute is now discouraged in favor of this new system.Explore starter templates for popular frameworks:
The ALTCHA widget is distributed as a Web Component and supports all modern browsers.
npm install altcha
Import in your main file:
import 'altcha';
Or load via <script>
tag:
<script async defer src="/altcha.js" type="module"></script>
CDN:
<script
async
defer
src="https://cdn.jsdelivr.net/gh/altcha-org/altcha@main/dist/altcha.min.js"
type="module"
></script>
<altcha-widget>
to Your Forms<form>
<altcha-widget challengeurl="https://..."></altcha-widget>
</form>
See configuration options or the website integration docs.
Refer to the server documentation for implementation details.
ALTCHA works on modern browsers with Web Crypto API support (specifically crypto.subtle
- caniuse.com).
Supported:
Not Supported:
ALTCHA is optimized for performance:
Distribution | Size (GZIPped) |
---|---|
ALTCHA | 29+ kB |
ALTCHA with all translations | 42+ kB |
hCaptcha | 48+ kB |
reCAPTCHA | 270+ kB |
When GZIPped, it totals about 29 kB, making ALTCHA’s widget about 90% smaller than reCAPTCHA.
The default bundle includes styles and workers in a single file. For strict CSP compliance, use scripts from /dist_external
. Learn more in the documentation.
Required options (at least one is required):
Additional options:
off
, onfocus
, onload
, onsubmit
).omit
, same-origin
, include
).fetch
function for retrieving the challenge.url: string
and init: RequestInit
as arguments and must return a Response
.false
).auto
, top
, bottom
).button[type="submit"]
in the related form).12
).true
| false
| focus
; defaults to false
, meaning the widget will hide).id
attribute. Useful for multiple instances of the widget on the same page.altcha/i18n/*
).fn:function_name
format to call a global JS function instead.navigator.hardwareConcurrency || 8
, max value 16
)../worker.js
, only works with external
build).Data Obfuscation options:
altcha/obfuscation
plugin). Use only without challengeurl
/challengejson
.Development / Testing options:
challengeurl
.ALTCHA supports 48+ languages. You can import individual language translations or a bundle that includes all of them.
To import all translations:
import 'altcha/i18n/all';
To import specific languages only:
import 'altcha/i18n/de';
import 'altcha/i18n/fr-fr';
Alternatively, you can import the combined bundle, which includes both the widget and all translations:
import 'altcha/i18n';
The widget automatically detects the language from:
<html lang="...">
attributenavigator.languages
)To override the language manually, use the language
attribute:
<altcha-widget language="de"></altcha-widget>
You can override default translations by updating the global altchaI18n
registry (globalThis.altchaI18n
or window.altchaI18n
):
import 'altcha/i18n/de';
globalThis.altchaI18n.set('de', {
...globalThis.altchaI18n.get('de'),
label: 'Ich bin ein Mensch', // Custom label
});
For additional verification, ALTCHA supports image/audio code challenges (e.g., "Enter the code from the image"). This feature requires ALTCHA Sentinel or a custom server implementation.
Extend functionality with plugins:
import 'altcha/obfuscation'; // Data obfuscation
import 'altcha/upload'; // File uploads
import 'altcha'; // Main package
Enable plugins per widget:
<altcha-widget plugins="upload,obfuscation"></altcha-widget>
To configure the widget programmatically, use the configure()
method:
document.querySelector('#altcha').configure({
challenge: {
algorithm: 'SHA-256',
challenge: '...',
salt: '...',
signature: '...',
},
strings: {
label: 'Verify',
},
});
Available configuration options:
export interface Configure {
auto?: 'off' | 'onfocus' | 'onload' | 'onsubmit';
challenge?: {
codeChallenge?: {
audio?: string;
image: string;
length?: number;
};
algorithm: string;
challenge: string;
maxnumber?: number;
salt: string;
signature: string;
};
challengeurl?: string;
credentials?: 'omit' | 'same-origin' | 'include' | boolean;
customfetch?:
| string
| ((url: string, init?: RequestInit) => Promise<Response>);
debug?: boolean;
delay?: number;
disableautofocus?: boolean;
expire?: number;
floating?: 'auto' | 'top' | 'bottom';
floatinganchor?: string;
floatingoffset?: number;
floatingpersist?: boolean | 'focus';
hidefooter?: boolean;
hidelogo?: boolean;
maxnumber?: number;
mockerror?: boolean;
name?: string;
obfuscated?: string;
refetchonexpire?: boolean;
spamfilter?: boolean | 'ipAddress' | SpamFilter; // deprecated
strings?: {
ariaLinkLabel: strin;
enterCode: string;
enterCodeAria: string;
error: string;
expired: string;
footer: string;
getAudioChallenge: string;
label: string;
loading: string;
reload: strin;
verificationRequired: string;
verified: string;
verifying: string;
waitAlert: string;
};
test?: boolean | number | 'delay';
verifyurl?: string;
workers?: number;
workerurl?: string;
}
spamfilter
).state
changes.enum State {
CODE = 'code',
ERROR = 'error',
VERIFIED = 'verified',
VERIFYING = 'verifying',
UNVERIFIED = 'unverified',
EXPIRED = 'expired',
}
Using events:
document.querySelector('#altcha').addEventListener('statechange', (ev) => {
// See enum State above
console.log('state:', ev.detail.state);
});
[!IMPORTANT]
Both programmatic configuration and event listeners have to called/attached after the ALTCHA script loads, such as withinwindow.addEventListener('load', ...)
.
See Contributing Guide and please follow our Code of Conduct.
This project is sponsored by BAUSW.com - Digital Construction Site Diary, promoting transparency and trust in construction projects with real-time documentation.
MIT