The only modern way to be sure an agent didn't fill in your form.
reCAPTCHA and Turnstile score a browser, and browsers run by language models pass. A Polyguard Trust Check replaces the checkbox: the visitor confirms with their phone in a few seconds, and your server verifies the result with one request. Two lines of HTML.
Visitors need a phone. They do not need to install an app.
Three steps
<script
src="https://cdn.polyguard.ai/sdk/latest/trust-check.js"
async defer></script><div class="pg-trust-check"
data-callback="onVerified"
data-action="signup"></div>
<!-- adds a hidden input:
pg-trust-check-response -->POST https://api.polyguard.ai/v2/siteverify
response=<session>&remoteip=203.0.113.9
{ "success": true,
"hostname": "www.example.com",
"score": "PASS",
"challenge_ts": "2026-10-22T18:40:12Z" }Your server compares hostname with its own, as with reCAPTCHA or Turnstile. A result can be verified once, within 300 seconds of the Trust Check.
Server-side verification in your stack
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sign up</title>
<script src="https://cdn.polyguard.ai/sdk/latest/trust-check.js" async defer></script>
</head>
<body>
<form method="POST" action="/signup">
<label>Email <input name="email" type="email" required></label>
<!-- On success the widget adds a hidden input named pg-trust-check-response. -->
<div class="pg-trust-check"
data-callback="onVerified"
data-expired-callback="onExpired"
data-error-callback="onError"
data-action="signup"></div>
<button id="submit" type="submit" disabled>Create account</button>
</form>
<script>
function onVerified() {
document.getElementById('submit').disabled = false;
}
function onExpired() {
document.getElementById('submit').disabled = true;
}
function onError(code) {
document.getElementById('submit').disabled = true;
console.warn('Trust Check error:', code);
}
</script>
</body>
</html>Why an AI agent cannot submit your form
A CAPTCHA hands the browser a puzzle, and browsers driven by language models now solve puzzles. PG-Presence asks for something a browser cannot produce: a physical phone in front of the screen.
Nothing for the agent to solve
The page shows a code that a phone camera has to read from the screen. An agent running in a browser, headless or not, has no camera to point at the screen, so the Trust Check never completes and the form stays locked.
Optical distance bounding
The code changes rapidly while the phone holds on it for a few seconds, and PG-Presence requires each capture within about 100 milliseconds of the code appearing on the screen. A relayed screenshot, a remote desktop or a screen share adds delay the bound does not allow.
Hardware attestation
Before a result counts, the phone proves it is a real device with hardware-backed attestation: Apple App Attest, Android key attestation or Google Play Integrity. A scripted browser or an emulator produces nothing your server will accept.
Your site, your account
The first Trust Check from a new hostname creates an account and a Custom app for that hostname. The account gets 1,000 Trust Checks per week, shared by every hostname and page you add to it. To manage it, sign in to the console and claim the hostname by publishing one file.
How claiming works{
"site_verification": {
"www.example.com": "pgsv_4Qm9…Xk2",
"example.com": "pgsv_8Lp2…Qa7"
}
}Serve it over https without a redirect. One file covers every hostname that points at your origin, each with its own token. A hostname belongs to one account.
Coming from reCAPTCHA or Turnstile?
Side-by-side diffs: one script URL, one class name, one hidden field, one endpoint.
Questions
What happens at 1,000 checks?
The widget shows "Unavailable this week" on every page in the account until Monday 00:00 UTC. Claim the hostname and contact us to raise the limit.
Do I need a secret key?
No. A result can be verified once and carries your hostname. After you claim the hostname you can require an API key on siteverify, or read the webhook instead.
Do visitors need to install an app?
They need a phone, not an app. iPhones complete the Trust Check in an App Clip, and Android phones in the browser. Visitors with Polyguard Mobile installed use it.
Does this work on localhost?
Yes, up to 100 Trust Checks a day per IP address. For unit tests use the SDK's testing double.
What does my server learn about the visitor?
Keyless mode requests PG-Presence only. The siteverify response carries the hostname, action, cdata, a presence score and timestamps. It has no name, email address or phone number, and none is asked of the visitor.
Can I ask for more than presence?
Not in this mode. Name, location, phone and document proofs are available on a claimed account through the Polyguard SDK.
My pages have dynamic paths. Is that a problem?
Each page path gets its own link in your account. For paths like /post/12345, set data-path="/post" so those pages share one.
Can a failed presence score fail the check?
Set data-scored="true". The default reports the score in the siteverify response without failing the check, which keeps the Android browser path open.
What the visitor needs
| Visitor on | Without the app | With Polyguard Mobile |
|---|---|---|
| iPhone | The hand-off page offers the App Clip in Safari. It completes the Trust Check with device attestation. | Opens Polyguard Mobile. |
| Android | The hand-off page runs a browser check with Android key attestation. Presence is reported as OFFLINE and does not fail the check. | Opens Polyguard Mobile. |
| Desktop | The visitor scans the code with their phone and continues as above. | Same. |
More in the Polyguard SDK docs. The source for this site and every example is on GitHub under the MIT license.