Developer Guides

reCAPTCHA v3 Token Integration — Server-Side Verification Guide

reCAPTCHA v3 produces a scored token. Integrating it correctly requires two parts: generating the token on the client (or via a solver API) and verifying it server-side using Google's siteverify endpoint. This guide covers both sides plus score-handling patterns.

For background on how reCAPTCHA v3 scoring works, see the reCAPTCHA Guide. For automating reCAPTCHA v3 (solving from a scraping/automation context), see How to Solve reCAPTCHA v3 in Python.

The v3 Token Flow

Client (browser)
  └─ grecaptcha.execute(SITEKEY, {action: 'login'})
  └─ → token (JWT-like string, expires in 2 minutes)
  └─ POST token to your backend

Backend
  └─ POST token + secret key to Google siteverify
  └─ ← {success, score, action, hostname}
  └─ Enforce: score >= threshold AND action matches

Client-Side Token Generation

<!-- Load reCAPTCHA v3 script -->
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>

<script>
// Generate a token on form submit
document.querySelector('#login-form').addEventListener('submit', async (e) => {
  e.preventDefault();

  const token = await grecaptcha.execute('YOUR_SITE_KEY', { action: 'login' });

  // Add token to form data before submitting
  document.querySelector('#recaptcha-token').value = token;
  e.target.submit();
});
</script>

<!-- Hidden field to carry the token in the form POST -->
<input type="hidden" id="recaptcha-token" name="g-recaptcha-response">

Action names: Use descriptive names that match the form context — login, register, checkout, contact. The action appears in the siteverify response and can be used for audit logging and per-action thresholds.

Server-Side Verification

Python (Flask / Django / plain requests)

import requests

RECAPTCHA_SECRET_KEY = "YOUR_SECRET_KEY"
RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"

def verify_recaptcha_v3(
    token: str,
    expected_action: str,
    min_score: float = 0.5,
    remote_ip: str | None = None,
) -> dict:
    """
    Verify a reCAPTCHA v3 token server-side.

    Returns a dict with keys:
      - success (bool): Whether the token is valid
      - score (float): Risk score 0.0–1.0 (1.0 = most likely human)
      - action (str): Action name from the token
      - passed (bool): Whether score >= min_score and action matches

    Raises RuntimeError on network/API errors.
    """
    payload = {
        "secret": RECAPTCHA_SECRET_KEY,
        "response": token,
    }
    if remote_ip:
        payload["remoteip"] = remote_ip

    r = requests.post(RECAPTCHA_VERIFY_URL, data=payload, timeout=10)
    r.raise_for_status()
    result = r.json()

    if not result.get("success"):
        errors = result.get("error-codes", [])
        return {"success": False, "passed": False, "errors": errors, "score": 0.0}

    score = result.get("score", 0.0)
    action = result.get("action", "")
    passed = score >= min_score and action == expected_action

    return {
        "success": True,
        "score": score,
        "action": action,
        "hostname": result.get("hostname"),
        "passed": passed,
    }


# Flask route example
from flask import Flask, request, jsonify, abort

app = Flask(__name__)

@app.route("/api/login", methods=["POST"])
def login():
    token = request.form.get("g-recaptcha-response")
    if not token:
        abort(400, "Missing CAPTCHA token")

    result = verify_recaptcha_v3(
        token=token,
        expected_action="login",
        min_score=0.5,
        remote_ip=request.remote_addr,
    )

    if not result["passed"]:
        # Log for monitoring — don't expose score to client
        app.logger.warning(
            f"reCAPTCHA v3 failed: score={result.get('score')}, "
            f"action={result.get('action')}, errors={result.get('errors')}"
        )
        abort(403, "Bot detection failed")

    # Proceed with login logic
    return jsonify({"status": "ok"})

Node.js (Express)

const express = require("express");
const axios = require("axios");

const RECAPTCHA_SECRET = process.env.RECAPTCHA_SECRET_KEY;
const VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify";

async function verifyRecaptchaV3(token, expectedAction, minScore = 0.5, remoteIp = null) {
  const params = new URLSearchParams({ secret: RECAPTCHA_SECRET, response: token });
  if (remoteIp) params.append("remoteip", remoteIp);

  const { data } = await axios.post(VERIFY_URL, params.toString(), {
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    timeout: 10000,
  });

  if (!data.success) {
    return { success: false, passed: false, errors: data["error-codes"] };
  }

  return {
    success: true,
    score: data.score,
    action: data.action,
    passed: data.score >= minScore && data.action === expectedAction,
  };
}

const app = express();
app.use(express.urlencoded({ extended: true }));

app.post("/api/contact", async (req, res) => {
  const token = req.body["g-recaptcha-response"];
  if (!token) return res.status(400).json({ error: "Missing CAPTCHA token" });

  const result = await verifyRecaptchaV3(token, "contact", 0.5, req.ip);
  if (!result.passed) {
    console.warn("reCAPTCHA v3 failed:", result);
    return res.status(403).json({ error: "Bot check failed" });
  }

  res.json({ status: "ok" });
});

Score Thresholds — What to Use

Use case Recommended threshold Notes
Public contact form 0.3 Low friction; allow most traffic
Login / register 0.5 Standard; blocks most bots
Password reset 0.7 Higher risk action
Payment / checkout 0.7–0.9 High-value; accept more friction
Admin access 0.9 Strict; expect some legitimate failures

Don't use a single threshold for all forms. Set per-action thresholds based on the value of the action and your tolerance for false positives.

Handling Low-Score Users

A score below threshold doesn't always mean a bot. Legitimate users on unusual networks (VPNs, corporate proxies, mobile data) can score low. Handle gracefully:

@app.route("/api/register", methods=["POST"])
def register():
    token = request.form.get("g-recaptcha-response")
    result = verify_recaptcha_v3(token, "register", min_score=0.5)

    if not result["passed"]:
        if result.get("score", 0) >= 0.3:
            # Borderline score — show a visible CAPTCHA challenge instead of hard block
            return jsonify({"status": "captcha_required"}), 200
        else:
            # Very low score — likely bot
            abort(403, "Bot detection failed")

    # Proceed with registration
    return jsonify({"status": "ok"})

siteverify Error Codes

Error code Cause Fix
missing-input-secret Secret key not sent Add secret to POST body
invalid-input-secret Wrong secret key Use the secret key from Google Console, not the site key
missing-input-response Token not sent Verify client-side JS generates and submits token
invalid-input-response Token malformed, expired, or already used Tokens are single-use; generate fresh per request
bad-request Request malformed Check Content-Type: application/x-www-form-urlencoded
timeout-or-duplicate Token > 2 minutes old or reused Generate token immediately before submission

Automating reCAPTCHA v3 (Solver Context)

If you're testing your own v3-protected forms or building automation against third-party v3 forms, use CaptchaAI to generate scored tokens:

# From the automation side: generate a v3 token via CaptchaAI
payload = {
    "key": captchai_api_key,
    "method": "userrecaptcha",
    "version": "v3",
    "googlekey": site_key,
    "pageurl": page_url,
    "action": "login",     # Must match your form's action
    "min_score": 0.7,
    "json": 1,
}
r = requests.post("https://ocr.captchaai.com/in.php", data=payload, timeout=30)

This token, when verified server-side, will return score >= 0.7 and action == "login" — passing your own verification logic.

Production Readiness Notes

Use reCAPTCHA v3 Token Integration — Server-Side Verification Guide as a decision and implementation aid, not just as a one-time reference. The practical test for recaptcha v3 token integration is whether the same approach behaves reliably when traffic is messy: rotating sessions, expired tokens, changing widget parameters, intermittent solver delays, and target pages that refresh without warning. For Backend developer implementing reCAPTCHA v3 on a web application, the safest rollout is to start with a narrow fixture, record every submitted task, and compare the solver response with the browser state that finally submits the form. That makes failures explainable instead of mysterious, especially when a target alternates between visible challenges, invisible checks, and server-side verification.

Evaluation Criteria

A how-to should be exercised against staging first, then promoted with feature flags so failed solves can fall back without blocking the entire workflow. For developer integration work, the most useful scorecard combines technical acceptance with operational cost. A low nominal price is not enough if retries double the real cost per accepted token, and a fast median solve time is not enough if p95 latency stalls the queue. Track these criteria before you standardize the workflow:

  • The challenge subtype, sitekey, action, rqdata, blob, captchaId, or page URL used for each task.
  • Median and p95 solve time, separated by provider and target domain.
  • Accepted-token rate on the target page, not just successful API responses.
  • Retry count, timeout count, zero-balance incidents, and invalid-parameter errors.
  • The exact browser, proxy region, and user-agent that submitted the solved token.

Rollout Checklist

Before this guidance moves into a production job, build a small acceptance suite around the pages that matter most. Run it with a fixed browser profile, then repeat with the proxy and concurrency settings you expect in production. Keep the first release conservative: bounded polling, clear timeout handling, and a fallback path when the solver cannot return a usable answer. For developer integration, treat the code as a production pattern: timeouts, retries, logging, secret storage, and test fixtures matter as much as the solve request itself. That checklist keeps the article useful after the first copy-paste, because the integration is judged by end-to-end completion rather than by whether a code sample returned a string.

Monitoring Signals

Healthy CAPTCHA automation is observable. Log the task id, provider, challenge type, target host, queue time, solve time, final submit status, and normalized error code for every attempt. Review those logs in daily batches at first, then move to alerts once the baseline is stable. Sudden drops usually come from target-side changes: a new sitekey, a changed action name, a stricter hostname check, an added managed challenge, or a proxy pool that no longer matches the expected geography. When you can see those shifts quickly, provider switching becomes a controlled decision instead of a late-night rewrite.

Maintenance Cadence

Revisit the setup whenever the target UI changes, when the solver provider changes task names or pricing, or when benchmark data shows a sustained latency or solve-rate shift. Keep one known-good fixture for each CAPTCHA subtype and rerun it after dependency upgrades, browser updates, and proxy changes. If the article is used for vendor selection, repeat the same fixture across at least two providers before renewing a balance or migrating the whole pipeline. That habit keeps recaptcha v3 token integration work aligned with the real target behavior rather than with stale assumptions.

Comments are disabled for this article.

Related Posts

Developer Guides How to Use a CAPTCHA Solver with Playwright
Step-by-step guide to integrating a CAPTCHA solver into Playwright automation.

Step-by-step guide to integrating a CAPTCHA solver into Playwright automation. Covers re CAPTCHA v 2, h Captch...

May 06, 2026
reCAPTCHA How to Solve reCAPTCHA v3 in Python
Complete Python tutorial for solving re CAPTCHA v 3 automatically — covers how v 3 scoring works, solver API parameters, min_score configuration, token injectio...

Complete Python tutorial for solving re CAPTCHA v 3 automatically — covers how v 3 scoring works, solver API p...

May 05, 2026
reCAPTCHA reCAPTCHA Guide — v2, v3, and Enterprise Explained
Complete guide to Google re CAPTCHA for developers — covers v 2 checkbox, v 2 invisible, v 3 score-based, and Enterprise variants, plus which solvers support ea...

Complete guide to Google re CAPTCHA for developers — covers v 2 checkbox, v 2 invisible, v 3 score-based, and...

May 03, 2026