1 · Sign up
Create a free account at trustedcaptcha.com/signup. Verify your email. The free plan covers 1,000 verifications per day with no credit card.
2 · Add a site
In the dashboard, click Add a site. Provide:
- A name (anything human-readable, just for your records)
- Your production hostnames, comma-separated. Wildcards like
*.example.comwork. Addlocalhostfor local development. - Default mode — leave on
autounless you have a specific reason. The risk engine picks the right challenge type per request.
You'll be given a sitekey (public, goes on your page) and a secret (private, lives on your server). The secret is shown once. Copy it now into a secure place — your server's environment, a secret manager, your .env file. We don't store it in plaintext on our side and can't show it again.
0x9a7f4b2c8e1d3f5a6b7c8d9e0f1a2b3c4d (36 chars).
Secret shape: 0x + 64 hex chars (66 total). Both prefixed with 0x so you can spot them in logs.
3 · Add the widget to your page
Include the loader script once, anywhere in your HTML. It's about 12 KB gzipped and auto-renders any element with the .trustedcaptcha class.
<!doctype html>
<html>
<head>
<script src="https://cdn.trustedcaptcha.com/widget/v1/api.js"
async defer></script>
</head>
<body>
<form action="/signup" method="POST">
<input name="email" type="email" required>
<input name="password" type="password" required>
<div class="trustedcaptcha"
data-sitekey="0x9a7f4b2c..."
data-theme="auto"></div>
<button type="submit">Sign up</button>
</form>
</body>
</html>
The widget renders as a checkbox initially (the same size as reCAPTCHA v2 — 304×78 px), validates passively, and only shows a challenge if the risk engine flags the request.
Available data-* attributes
| Attribute | Values | Default |
|---|---|---|
data-sitekey | Your sitekey | required |
data-theme | auto, light, dark | auto |
data-size | normal, compact, invisible | normal |
data-mode | auto, smart, image, math, logic, audio, invisible | auto |
data-language | auto, en, de, fr, es | auto |
data-callback | Name of a global function called with the token | — |
data-expired-callback | Called when the token expires (after 180s unused) | — |
data-error-callback | Called on any widget error | — |
4 · Verify on your server
When the form submits, your form's POST body contains a hidden field trustedcaptcha-response with a 40-byte token. On your server, POST it to the verify endpoint with your secret to validate.
# Endpoint
POST https://challenges.trustedcaptcha.com/api/v1/siteverify
# Form-encoded body
secret=YOUR_SECRET
response=USER_TOKEN
remoteip=USER_IP # optional but recommended
# Response (reCAPTCHA-compatible JSON)
{
"success": true,
"score": 0.92,
"mode": "smart",
"hostname": "example.com",
"challenge_ts": "2026-05-11T09:00:00+00:00",
"action": null,
"error_codes": []
}
PHP example
$response = file_get_contents(
'https://challenges.trustedcaptcha.com/api/v1/siteverify'
. '?secret=' . urlencode($SECRET)
. '&response=' . urlencode($_POST['trustedcaptcha-response'])
. '&remoteip=' . urlencode($_SERVER['REMOTE_ADDR'])
);
$result = json_decode($response, true);
if (!$result['success']) {
http_response_code(400);
exit('Captcha verification failed');
}
// $result['score'] is 0.0 (bot) to 1.0 (human). Threshold per your risk tolerance.
if ($result['score'] < 0.3) {
// Add to manual review queue, request 2FA, etc.
}
Examples for Node.js, Python, Ruby, Go, and curl are in the developers guide.
5 · Compatibility with existing reCAPTCHA / hCaptcha / Turnstile code
The widget fills four hidden inputs simultaneously — trustedcaptcha-response plus g-recaptcha-response, h-captcha-response, and cf-turnstile-response. If your server already reads any of these from $_POST, no server-side changes are needed.
The /api/v1/siteverify endpoint accepts the same parameters as reCAPTCHA's siteverify (secret, response, remoteip) and returns the same JSON shape (success, score, challenge_ts, hostname, error_codes). The only field that's different is the endpoint URL.
Error codes
If success is false, the error_codes array tells you why:
| Code | Meaning |
|---|---|
missing-input-secret | The secret field is empty. |
invalid-input-secret | The secret doesn't match any site. |
missing-input-response | The token field is empty. |
invalid-input-response | The token doesn't exist or is malformed. |
timeout-or-duplicate | Token expired (over 180s old) or already verified. |
hostname-mismatch | The parent page's hostname isn't in this site's allowlist. |
rate-limited | Too many verify calls for this secret. Back off. |
Next steps
- Read the widget reference for programmatic control (
reset(),execute(), callbacks). - See API reference for the full
/challengeand/answerendpoints (you typically don't need them — the widget handles both — but they're documented for SDK authors). - Configure site settings to enable accessibility-only mode, change the default challenge type, or adjust the score threshold.