Skip to main content
agenthouse Academy AI Enablement Overview DealDesk Digital Customer Assistant HumanProof Decision Intelligence NewsRoom Anonymizer Agentic Business Agents agenthouse OS Services Developers AI Credits Deutsch English Italiano Français Español 日本語
No cookies Single-file script Backend-verified

Classic CAPTCHAs slow people down, strain accessibility, and often pull in tracking-heavy third-party scripts. HumanProof uses a short-lived signed challenge and lightweight proof-of-work in the browser—no puzzles, images, or extra widgets.

  • No fingerprinting and no cross-site tracking profile from a widget network.
  • No Google or external CAPTCHA provider required on your page.
  • Secrets stay on your server: the browser never holds the verify key.

From challenge to verified submission in four steps.

  1. The SDK requests a signed challenge for your public site key and form id.
  2. The browser solves a small proof-of-work (off the main thread when possible).
  3. The proof is attached as a hidden field on your normal form submit.
  4. Your server calls the verify API with the secret key and receives accept, quarantine, or fake-success guidance.

A good fit for GDPR-conscious teams and EU-first hosting: small surface area, no ad-tech scripts on your form.

  • No cookies required for the widget.
  • Traffic stays between your origin and the agenthouse API—no ad-network scripts.
  • Verification-side logging is kept minimal and retention-aware.
  • You decide how to act on each result—accept, quarantine, or fake-success—on your own backend.

No npm, no bundler, no framework: load one script from the agenthouse API, set data attributes on your forms, and you are done. One script can protect many forms on the same page. Optional “Powered by agenthouse” line included by default.

Frontend: add one script. Backend: one JSON POST to verify. Works with any stack—Node.js, PHP, Python, or anything that can call HTTPS.

Backend: POST the same JSON body to …/humanproof/verify. The response includes status, suggestedAction, reasonCode, and accept (boolean — true only when the submission should be treated as verified). Call from your server only—never send secretKey in the browser.

Some forms require a hostname field on verify (it must match boundHost from the client payload—the host used when the challenge was issued). If requireVerifyHostname is enabled, omitting hostname returns reasonCode HOSTNAME_REQUIRED. 127.0.0.1 and localhost are different; allow both in hostnames or align your site URL and data-challenge-hostname with the verify body.

Script and API URLs on this page follow your configuration (production defaults to api.agenthouse.org; set humanproof.publicSdkBase in secrets.json to point at a dev API).

When the SDK must fetch a challenge and solve proof-of-work on submit, it dispatches the CustomEvent humanproof.securingForm on window (detail includes form, siteKey, and formId) so your app can show progress. When securing completes and the signed proof is attached—immediately before the native submit—it dispatches agenthouse_humanproof_verified_submit on the form (bubbles; detail includes form, siteKey, formId, and proof metadata). That event means client-side securing succeeded only; your backend must still call verify. On failure it dispatches humanproof-error on the form.

window.addEventListener('humanproof.securingForm', function (e) {
  /* e.detail.form, siteKey, formId */
});
form.addEventListener('agenthouse_humanproof_verified_submit', function (e) {
  /* e.detail.form, siteKey, formId, proof.challengeId, proof.proof, … */
});

By default the SDK locks the form (input, select, textarea, button — anything not already disabled) while it fetches the challenge and solves proof-of-work. The form gets data-humanproof-securing=1 and aria-busy=true for styling and accessibility, and is unlocked just before submission (or immediately on error). Turn it off with data-lock-while-securing=false on the script, or per-form via data-humanproof-lock-while-securing=false.

You cannot solve proof-of-work before the server issues a challenge. Optional: set data-prefetch-challenge-delay-ms on the script (milliseconds after load) to fetch a challenge and solve in the background; submit then reuses the proof while it is still valid. Use data-prefetch-expiry-margin-ms to require that much remaining TTL before reuse (clock skew). Per-form overrides: data-humanproof-prefetch-challenge-delay-ms and data-humanproof-prefetch-expiry-margin-ms on the form.

HTML HTML multi-form Node.js PHP Python
<form id="contact-form" data-humanproof="YOUR_FORM_ID">
  <label>Email <input type="email" name="email" required></label>
  <button type="submit">Send</button>
</form>
<script
  src="https://api.agenthouse.org/humanproof/humanproof.js"
  data-site-key="YOUR_PUBLIC_SITE_KEY"
  data-powered-by="true"
  data-endpoint="https://api.agenthouse.org/humanproof"
  data-prefetch-challenge-delay-ms="2500"
  data-prefetch-expiry-margin-ms="15000"
></script>
<form id="a" data-humanproof="form-a">...</form>
<form id="b" data-humanproof="form-b">...</form>
<script
  src="https://api.agenthouse.org/humanproof/humanproof.js"
  data-site-key="YOUR_PUBLIC_SITE_KEY"
  data-powered-by="true"
  data-endpoint="https://api.agenthouse.org/humanproof"
  data-prefetch-challenge-delay-ms="2500"
  data-prefetch-expiry-margin-ms="15000"
></script>
const verifyUrl = 'https://api.agenthouse.org/humanproof/verify';
const secretKey = ''; // set from your secrets store before use
const payload = JSON.parse(formFields.humanproof);
const body = { secretKey, ...payload };
if (typeof body.hostname !== 'string' || !body.hostname.trim()) {
  if (typeof body.boundHost === 'string' && body.boundHost.trim()) {
    body.hostname = body.boundHost.trim();
  }
}
const result = await (await fetch(verifyUrl, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
})).json();
if (!result.accept) return; // do not process as a verified submit
<?php
$url = 'https://api.agenthouse.org/humanproof/verify';
$secret = ''; // set from your server secret store before use
$payload = json_decode($_POST['humanproof'], true);
$body = array_merge(['secretKey' => $secret], $payload);
if (empty($body['hostname']) && !empty($body['boundHost'])) {
  $body['hostname'] = $body['boundHost'];
}
$ch = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode($body),
  CURLOPT_RETURNTRANSFER => true,
]);
$out = curl_exec($ch);
$result = json_decode($out, true);
// if (empty($result['accept'])) { return; }
?>
import json
import urllib.request

VERIFY_URL = "https://api.agenthouse.org/humanproof/verify"

def verify_humanproof(secret_key: str, payload: dict) -> dict:
    body = {"secretKey": secret_key, **payload}
    if not (isinstance(body.get("hostname"), str) and body["hostname"].strip()):
        bh = body.get("boundHost")
        if isinstance(bh, str) and bh.strip():
            body["hostname"] = bh.strip()
    req = urllib.request.Request(
        VERIFY_URL,
        data=json.dumps(body).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        r = json.load(resp)
    # if not r.get("accept"): return
    return r

HumanProof shows a small “Powered by agenthouse” line by default. Remove it with a simple monthly subscription—same product, same privacy guarantees.

  • Small “Powered by” line included on the free tier.
  • Paid plan hides the line; behaviour and verification stay identical.
  • Contact us when you are ready to license.

Practical wins for product and compliance teams alike.

  • Fewer interruptions than image or puzzle CAPTCHAs—nothing new for users to click.
  • Better accessibility: no visual challenge step, works with keyboard-driven flows.
  • Strong privacy posture: no third-party CAPTCHA or ad widget on your form.
  • Fast to ship: one script, one verify API, examples for common backends.
  • Policy you control: per-form difficulty and how to treat suspicious submits.

Submit the form below: proof-of-work runs in the background, then the demo posts to our server (nothing is stored).

Demo result

Configure your site key and per-form rules in agenthouse, add the script, and verify every submission on your backend. That is the whole integration—we are here if you want help rolling out.

agenthouse Privacy Imprint