Installation
Load the embed script and mark a container. Everything else is automatic.
<script src="https://privcaptcha.com/1/api.js" async defer></script>
<div class="privcaptcha" data-sitekey="pk_your_sitekey"></div>
The script is cached for 10 minutes, so a widget fix reaches your visitors the same day without you redeploying anything.
Container attributes
| Attribute | Required | Meaning |
|---|---|---|
class="privcaptcha" |
yes, for automatic rendering | marks the container the loader scans for |
data-sitekey |
yes | your public sitekey |
data-callback |
no | name of a global function called with the token on success |
<div class="privcaptcha"
data-sitekey="pk_your_sitekey"
data-callback="onCaptchaSolved"></div>
<script>
function onCaptchaSolved(token) {
document.querySelector('#submit').disabled = false;
}
</script>
The response field
On success the token is written into a hidden input named privcaptcha-response inside the
container. A normal form submit carries it; you do not need any JavaScript.
Read it server-side as you would any form field, then
verify it. If you submit with fetch() instead, read the token with
privcaptcha.getResponse().
Explicit rendering
Skip the privcaptcha class and render when you are ready — useful for dynamically inserted
forms, modals or single-page apps.
<div id="captcha-slot"></div>
<script>
var id = privcaptcha.render('captcha-slot', {
sitekey: 'pk_your_sitekey',
callback: function (token) { console.log(token); },
'expired-callback': function () { console.log('token expired'); },
'error-callback': function (err) { console.error(err); },
});
</script>
render() accepts an element or an element id and returns a widget id you can pass to the
other JavaScript API methods. It returns null if no sitekey
was given.
Layout
The badge is a 302×76 iframe. The puzzle opens in a centred overlay above the page
(z-index: 2147483647), dismissible with Escape or a click outside. Nothing is injected
into your page's styles, and the iframes carry a frame-ancestors policy scoped to your
site's own domains, so no one else can frame your
widget.
The iframe endpoints
The loader builds these itself; you never link to them directly, but they show up in a CSP report or a network tab, so they are listed here.
| Path | What it is |
|---|---|
/widget/badge |
the 302×76 checkbox iframe |
/widget/challenge |
the puzzle iframe inside the overlay |
Both take the sitekey as a query parameter and answer with a
Content-Security-Policy: frame-ancestors header scoped to that site's domains, so only
your own pages can frame them. A site with an empty domain list gets *, so a first
integration works before the list is configured; an unknown sitekey gets 'none'. Both are
noindex.
Lifetime
A token is valid for 120 seconds server-side. The widget clears it after 110 seconds, resets
itself and fires expired-callback, so a slowly-filled form never submits a dead token.