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.