JavaScript API
/1/api.js exposes a single global, window.privcaptcha. Every method takes an optional
widget id; omit it and the first widget on the page is used.
Methods
privcaptcha.render(container, params)
Renders a widget and returns its id, or null if no sitekey was resolved.
var id = privcaptcha.render('captcha-slot', { sitekey: 'pk_...' });
| Param | Type | Meaning |
|---|---|---|
sitekey |
string | your public sitekey; falls back to the container's data-sitekey |
callback |
function | called with the token on success; falls back to the global named by data-callback |
expired-callback |
function | called when the local token lifetime lapses |
error-callback |
function | called with the error when a challenge or solve request fails |
responseField |
string | name of the hidden input holding the token (default privcaptcha-response) |
container may be an element or an element id.
privcaptcha.getResponse(id)
Returns the current token, or '' if the widget is unsolved.
fetch('/signup', {
method: 'POST',
body: new URLSearchParams({
email: email.value,
'privcaptcha-response': privcaptcha.getResponse(),
}),
});
privcaptcha.reset(id)
Clears the token and the hidden input, closes the puzzle if it is open, and returns the badge to its unchecked state. Call it whenever your server rejects a submission — tokens are single-use, so the old one cannot be sent again.
privcaptcha.execute(id)
Opens the puzzle and requests a challenge programmatically, without the visitor ticking the checkbox. Use it for invisible-style flows where the captcha runs on submit.
form.addEventListener('submit', function (e) {
if (!privcaptcha.getResponse()) {
e.preventDefault();
privcaptcha.execute();
}
});
Callbacks
privcaptcha.render('slot', {
sitekey: 'pk_...',
callback: function (token) { /* solved */ },
'expired-callback': function () { /* token cleared locally */ },
'error-callback': function (err) { /* network or API failure */ },
});
error-callback fires for a genuine failure. It does not fire when the account is out of
quota and credits: that case is not an error — the badge shows a quiet unavailable state and
the host page is left untouched. If you need to distinguish "over limit" from "widget broke",
that is the difference.
What runs where
The parent page — not the iframe — makes every API call. That is deliberate: a request from
your page is cross-origin to us, so the browser attaches the Origin header itself, and
Origin is a header page JavaScript cannot set. It is the one trustworthy statement about
which site is embedding the widget, and it is what the domain allowlist checks. A call from
inside our own iframe would be same-origin and carry no Origin at all.
The iframes are purely presentational: they draw the puzzle and report the visitor's input by
postMessage, origin-checked in both directions. The answer never leaves the server, so it
does not matter that the host page can see the challenge payload.