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.
Names and endpoints
| Cloudflare Turnstile | Polyguard |
|---|---|
| Remove: challenges.cloudflare.com/turnstile/v0/api.js | Add: cdn.polyguard.ai/sdk/latest/trust-check.js |
| Remove: class="cf-turnstile" data-sitekey | Add: class="pg-trust-check" |
| Remove: cf-turnstile-response | Add: pg-trust-check-response |
| Remove: turnstile.render/reset/remove/getResponse/isExpired/execute/ready | Add: same names under Polyguard.TrustCheck |
| Remove: data-action, data-cdata, data-theme, data-size, data-execution | Add: same |
| Remove: data-appearance, managed/non-interactive/invisible modes | Add: not supported; every check is interactive |
| Remove: /turnstile/v0/siteverify | Add: /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 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. |