Quickstart
You need a sitekey and a secret. Create a site in the dashboard at /en/sites; the secret is shown there and can be re-displayed at any time.
1. Add the widget
<form action="/signup" method="post">
<input name="email" type="email" required>
<script src="https://privcaptcha.com/1/api.js" async defer></script>
<div class="privcaptcha" data-sitekey="pk_your_sitekey"></div>
<button type="submit">Sign up</button>
</form>
The script scans the page for .privcaptcha elements on load and renders each one. When the
visitor solves the puzzle, the token is written into a hidden input named
privcaptcha-response, which submits with the form.
2. Verify on the server
Never trust the form field on its own — redeem it.
import os
from privcaptcha import verify
result = verify(
secret=os.environ["PRIVCAPTCHA_SECRET"],
response=request.form["privcaptcha-response"],
remote_ip=request.remote_addr,
)
if not result.success:
abort(400, result.error_codes)
Or with plain HTTP, from any language:
curl -X POST https://privcaptcha.com/api/v1/siteverify \
-d "secret=sk_your_secret" \
-d "response=THE_TOKEN_FROM_THE_FORM"
{
"success": true,
"challenge_ts": "2026-08-10T12:34:56+00:00",
"hostname": "example.com",
"error-codes": []
}
Ready-made helpers exist for Python, Node, PHP and Go.
3. Lock down the domains
While a site's domain list is empty, any origin may use its sitekey — that is deliberate, so your first integration works before you configure anything. Add your production hostnames in the dashboard as soon as the integration works. See Sites and domains.
Things worth knowing
- A token is single-use and lives for 120 seconds. Verify it as part of handling the submit, not later in a queue.
- The browser clears the token after 110 seconds and resets the widget, so a slowly-filled form does not submit a token that is already dead.
- If verification fails, call
privcaptcha.reset()before asking the visitor to try again; a spent token cannot be reused. - Fail closed. If you cannot reach
/siteverify, treat the request as unverified. Every official SDK already does this and returnsverification-unavailable.