Developers

Two snippets. Two minutes.

One on the page, one on your server. Examples for the seven most common backend languages. The token shape is reCAPTCHA-compatible — if you already validate g-recaptcha-response, this works.

— Step 01

Get a sitekey + secret

Sign up, then Sites → Add a site. You'll get a sitekey (public, embedded on your page) and a secret (private, used by your server). Sitekeys look like 0x9a7f4b2c…; secrets are 66 characters.

— Step 02

Add the widget to your page

Drop the loader script into your HTML and add a .trustedcaptcha element wherever you want the widget. It auto-renders when the DOM is ready.

<script src="https://cdn.trustedcaptcha.com/widget/v1/api.js" async defer></script>

<form action="/signup" method="POST">
  <input name="email" type="email" required>

  <div class="trustedcaptcha"
       data-sitekey="0x9a7f4b2c..."
       data-theme="auto"></div>

  <button type="submit">Sign up</button>
</form>

When the user passes, the widget fills a hidden input named trustedcaptcha-response (plus g-recaptcha-response, h-captcha-response, cf-turnstile-response for compatibility) with a 40-byte token. Submit the form normally.

— Step 03

Verify the token on your server

POST to the verify endpoint with your secret and the token. Returns reCAPTCHA-compatible JSON.

$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 the risk score (0.0 = bot, 1.0 = human)
const r = await fetch('https://challenges.trustedcaptcha.com/api/v1/siteverify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    secret: process.env.TC_SECRET,
    response: req.body['trustedcaptcha-response'],
    remoteip: req.ip,
  }),
});
const result = await r.json();
if (!result.success) return res.status(400).send('Captcha verification failed');
// result.score
import requests
r = requests.post('https://challenges.trustedcaptcha.com/api/v1/siteverify', data={
    'secret':   os.environ['TC_SECRET'],
    'response': request.form['trustedcaptcha-response'],
    'remoteip': request.remote_addr,
})
result = r.json()
if not result['success']:
    abort(400, 'Captcha verification failed')
# result['score']
require 'net/http'
require 'uri'
require 'json'

uri = URI('https://challenges.trustedcaptcha.com/api/v1/siteverify')
res = Net::HTTP.post_form(uri, {
  'secret'   => ENV['TC_SECRET'],
  'response' => params['trustedcaptcha-response'],
  'remoteip' => request.remote_ip,
})
result = JSON.parse(res.body)
halt 400, 'Captcha verification failed' unless result['success']
resp, _ := http.PostForm("https://challenges.trustedcaptcha.com/api/v1/siteverify", url.Values{
    "secret":   {os.Getenv("TC_SECRET")},
    "response": {r.FormValue("trustedcaptcha-response")},
    "remoteip": {r.RemoteAddr},
})
defer resp.Body.Close()
var result struct {
    Success bool    `json:"success"`
    Score   float64 `json:"score"`
}
json.NewDecoder(resp.Body).Decode(&result)
if !result.Success {
    http.Error(w, "Captcha verification failed", 400)
    return
}
curl -X POST https://challenges.trustedcaptcha.com/api/v1/siteverify \
  -d "secret=YOUR_SECRET" \
  -d "response=USER_TOKEN" \
  -d "remoteip=USER_IP"

# Response:
# {"success":true, "score":0.92, "mode":"smart", "hostname":"example.com",
#  "challenge_ts":"2026-05-11T09:00:00+00:00", "action":null, "error_codes":[]}

Full documentation

All endpoints, all error codes, all configuration options.

/docs/quickstart →

Migrating from reCAPTCHA

One-line change. Existing form code keeps working.

/migrate/recaptcha →

Changelog

API and widget version history.

/changelog →