postcaptcha
Migration guide

From Cloudflare Turnstile to a Polyguard Trust Check

Change one script URL, one class name, one hidden field name and one server call. Your server keeps comparing hostname, as Turnstile already asks you to.

From reCAPTCHA v2

Names and endpoints

Cloudflare TurnstilePolyguard
Remove: challenges.cloudflare.com/turnstile/v0/api.jsAdd: cdn.polyguard.ai/sdk/latest/trust-check.js
Remove: class="cf-turnstile" data-sitekeyAdd: class="pg-trust-check"
Remove: cf-turnstile-responseAdd: pg-trust-check-response
Remove: turnstile.render/reset/remove/getResponse/isExpired/execute/readyAdd: same names under Polyguard.TrustCheck
Remove: data-action, data-cdata, data-theme, data-size, data-executionAdd: same
Remove: data-appearance, managed/non-interactive/invisible modesAdd: not supported; every check is interactive
Remove: /turnstile/v0/siteverifyAdd: /v2/siteverifyidempotency_key accepted and ignored in v1

Markup

Cloudflare Turnstile
Polyguard
Removed: <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
Added: <script src="https://cdn.polyguard.ai/sdk/latest/trust-check.js" async defer></script>
<form method="POST" action="/signup">
<form method="POST" action="/signup">
<input name="email" type="email" required>
<input name="email" type="email" required>
Removed: <div class="cf-turnstile"
Added: <div class="pg-trust-check"
Removed: data-sitekey="YOUR_SITE_KEY"
data-callback="onVerified"
data-callback="onVerified"
data-action="signup"></div>
data-action="signup"></div>
<button type="submit">Create account</button>
<button type="submit">Create account</button>
</form>
</form>

Server-side verification

Cloudflare Turnstile
Polyguard
Removed: const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
Added: const res = await fetch('https://api.polyguard.ai/v2/siteverify', {
method: 'POST',
method: 'POST',
body: new URLSearchParams({
body: new URLSearchParams({
Removed: secret: process.env.TURNSTILE_SECRET,
Added: response: req.body['pg-trust-check-response'],
Removed: response: req.body['cf-turnstile-response'],
remoteip: req.ip,
remoteip: req.ip,
}),
}),
});
});
const result = await res.json();
const result = await res.json();
if (!result.success || result.hostname !== 'www.example.com') {
if (!result.success || result.hostname !== 'www.example.com') {
Removed: return res.status(403).send('CAPTCHA failed');
Added: return res.status(403).send('Trust Check failed');
}
}

Versions for Next.js, Express, Flask, PHP and WordPress are in the examples directory.

What is different

  • No site key or account is needed to start. The first Trust Check from your hostname creates the account.
  • secret is optional. After you claim the hostname, send the App API key as secret, and require it from the Advanced tab if you want.
  • remoteip is compared with the address that started the Trust Check and reported as remoteip_matched. It never changes success, because mobile carriers and corporate egress change addresses between the click and the form post.
  • A result verifies once, within 300 seconds of completion. A second call returns timeout-or-duplicate.
  • The response adds app_id, link_uuid, link_session_id, action, cdata and score.
  • Visitors need a phone. They do not need to install an app.
  • Sizes match Turnstile: normal (300×65), compact (150×140) and flexible (full width, 65 tall).

What the visitor needs

Visitor onWithout the appWith Polyguard Mobile
iPhoneThe hand-off page offers the App Clip in Safari. It completes the Trust Check with device attestation.Opens Polyguard Mobile.
AndroidThe 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.
DesktopThe visitor scans the code with their phone and continues as above.Same.