Explainers

reCAPTCHA Enterprise Assessment API: подробный разбор

Для скрипта автоматизации reCAPTCHA Enterprise неотличима от v3: тот же виджет, тот же формат токена. Асимметрия начинается на сервере владельца сайта — там работает Assessment API, возвращающий не только балл, но и его причины с метками Account Defender.

Отсюда три следствия:

  • проект в Google Cloud нужен оператору сайта, а не получателю токена;
  • на клиенте меняются две вещи — имя скрипта и имя объекта JS;
  • в запросе к сервису решения добавляется флаг enterprise=1.

Как выглядит поток запроса: клиент отдельно, сервер отдельно

Из последовательности вызовов видно, что воспроизводится на клиенте.

Client-side:

  1. Load reCAPTCHA Enterprise script
  2. Call grecaptcha.enterprise.execute(SITE_KEY, {action: 'LOGIN'})
  3. Receive token
  4. Send token to your backend

Server-side:

  1. Create assessment via Enterprise API
  2. Receive detailed risk analysis
  3. Make access decision based on score + reasons
  4. Optionally annotate the assessment (report fraud/legitimate)

Шаги 1–4 выполняет браузер посетителя — их и воспроизводит сервис решения.

  • для получения токена достаточно клиентской половины;
  • всё, ради чего покупают Enterprise, — в серверной;
  • «токен есть, доступа нет» живёт на стыке половин.

Что Enterprise добавляет к бесплатной reCAPTCHA v3

Возможность reCAPTCHA v3 (бесплатно) reCAPTCHA Enterprise
Оценка балл 0,0–1,0 балл 0,0–1,0 + причины оценки
Анализ рисков базовый детальный (сигналы мошенничества, данные аккаунта)
Причины оценки нет конкретные причины балла
Account Defender нет да (жизненный цикл аккаунта)
Интеграция с WAF нет да (Cloudflare, Fastly, F5)
Express assessment нет да (только на стороне сервера, без JS)
Обнаружение утечки пароля нет да
Стоимость бесплатно (1 000 000 оценок/мес) $1 за 1000 оценок сверх 1 000 000
Конечная точка API google.com/recaptcha/api/siteverify recaptchaenterprise.googleapis.com

Работу клиента это не меняет: за оценки платит оператор сайта.


Клиентская часть: как Enterprise виден в разметке

Три отличия в JavaScript SDK

Отличий от v3 три:

  • вместо .../recaptcha/api.js в URL стоит .../recaptcha/enterprise.js;
  • объект API — grecaptcha.enterprise, а не grecaptcha;
  • execute() возвращает токен того же формата.
<script src="https://www.google.com/recaptcha/enterprise.js?render=SITE_KEY"></script>
<script>
    grecaptcha.enterprise.ready(function() {
        grecaptcha.enterprise.execute('SITE_KEY', { action: 'LOGIN' })
            .then(function(token) {
                // Send token to backend
                fetch('/api/verify', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ token: token })
                });
            });
    });
</script>

Метка action сверяется на сервере — к ней вернёмся ниже.

Определение варианта по исходнику страницы

Определяйте вариант заранее: ошибка здесь — частая причина отказа.

import requests
import re

def detect_recaptcha_enterprise(url):
    """Detect if a page uses reCAPTCHA Enterprise."""
    html = requests.get(url, timeout=10).text

    indicators = {
        "is_enterprise": False,
        "is_standard": False,
        "site_key": None,
        "actions": [],
    }

    # Enterprise detection
    if "recaptcha/enterprise.js" in html:
        indicators["is_enterprise"] = True
        match = re.search(r"render=([A-Za-z0-9_-]+)", html)
        if match:
            indicators["site_key"] = match.group(1)

    # Standard v3 detection
    elif "recaptcha/api.js?render=" in html:
        indicators["is_standard"] = True
        match = re.search(r"render=([A-Za-z0-9_-]+)", html)
        if match:
            indicators["site_key"] = match.group(1)

    # Extract action names
    actions = re.findall(r"action:\s*['\"](\w+)['\"]", html)
    indicators["actions"] = list(set(actions))

    return indicators

print(detect_recaptcha_enterprise("https://staging.example.com/qa-login"))

Она же вытаскивает sitekey и значения action.


Серверная часть: что возвращает Assessment API

Создание оценки в Google Cloud

from google.cloud import recaptchaenterprise_v1
from google.cloud.recaptchaenterprise_v1 import Assessment

def create_assessment(project_id, site_key, token, action):
    """Create a reCAPTCHA Enterprise assessment."""
    client = recaptchaenterprise_v1.RecaptchaEnterpriseServiceClient()

    event = recaptchaenterprise_v1.Event()
    event.site_key = site_key
    event.token = token
    event.expected_action = action

    assessment = recaptchaenterprise_v1.Assessment()
    assessment.event = event

    request = recaptchaenterprise_v1.CreateAssessmentRequest()
    request.assessment = assessment
    request.parent = f"projects/{project_id}"

    response = client.create_assessment(request)
    return response

Структура ответа

{
    "name": "projects/123456/assessments/abcdef123",
    "event": {
        "token": "...",
        "siteKey": "6Le...",
        "expectedAction": "LOGIN",
        "hashedAccountId": "abc123..."
    },
    "riskAnalysis": {
        "score": 0.9,
        "reasons": [
            "AUTOMATION",
            "TOO_MUCH_TRAFFIC"
        ],
        "extendedVerdictReasons": [
            "BROWSER_ERROR"
        ]
    },
    "tokenProperties": {
        "valid": true,
        "hostname": "example.com",
        "action": "LOGIN",
        "createTime": "2025-01-15T10:30:00Z",
        "invalidReason": ""
    },
    "accountDefenderAssessment": {
        "labels": ["PROFILE_MATCH"]
    }
}

riskAnalysis.reasons и accountDefenderAssessment есть только в ответе Assessment API: в браузер они не попадают, и на чужом сайте причины балла не видны.

Блок tokenProperties полезен иначе: valid, hostname и action показывают, корректно ли собран запрос.


Причины оценки: почему балл занижен

Enterprise называет причину занижения. Третья колонка — порядок влияния, не коэффициент.

Причина Что означает Влияние на балл
AUTOMATION автоматизированный user-agent или браузер в headless-режиме от -0,3 до -0,7
UNEXPECTED_ENVIRONMENT несоответствие окружения браузера или устройства от -0,2 до -0,4
TOO_MUCH_TRAFFIC много запросов с этого IP или сессии от -0,1 до -0,3
UNEXPECTED_USAGE_PATTERNS поведение расходится с человеческим от -0,2 до -0,5
LOW_CONFIDENCE_SCORE данных для уверенной оценки мало переменное
SUSPECTED_CARDING признаки мошенничества с картами от -0,3 до -0,6
SUSPECTED_CHARGEBACK риск возврата платежа от -0,2 до -0,4

extendedVerdictReasons описывает не поведение, а технику запроса.

Extended verdict reasons

Причина Что означает
BROWSER_ERROR ошибки JavaScript в SDK CAPTCHA
SITE_MISMATCH токен выпущен для другого сайта
FAILED_TWO_FACTOR недавний провал двухфакторной аутентификации

Account Defender: оценка аккаунта, а не запроса

Account Defender смотрит на историю аккаунта, поэтому метки срабатывают и на безупречном запросе.

{
    "accountDefenderAssessment": {
        "labels": [
            "PROFILE_MATCH",
            "SUSPICIOUS_LOGIN_ACTIVITY",
            "SUSPICIOUS_ACCOUNT_CREATION",
            "RELATED_ACCOUNTS_NUMBER_HIGH"
        ]
    }
}
Метка Значение
PROFILE_MATCH поведение совпадает с профилем аккаунта
SUSPICIOUS_LOGIN_ACTIVITY вход с нового устройства или региона
SUSPICIOUS_ACCOUNT_CREATION регистрация выглядит автоматизированной
RELATED_ACCOUNTS_NUMBER_HIGH много аккаунтов на одном устройстве

Правило для QA-стендов: синтетические учётные записи под каждый прогон.


Интеграция с WAF: проверка до того, как запрос дойдёт до приложения

При подключении к WAF проверка появляется не на форме, а на промежуточном экране.

Cloudflare WAF

Request arrives at Cloudflare edge
    ↓
Cloudflare WAF rule evaluates request
    ↓
Rule triggers reCAPTCHA Enterprise challenge
    ↓
Client solves CAPTCHA → token returned
    ↓
Cloudflare validates token via Enterprise API
    ↓
If valid + score above threshold → request forwarded to origin

F5 BIG-IP

F5 iRule or policy evaluates request
    ↓
Triggers reCAPTCHA Enterprise challenge page
    ↓
Client solves → token validated server-side
    ↓
F5 forwards or blocks based on assessment score

Отсюда правило: pageurl — URL с виджетом, а не адрес формы.


Enterprise в автоматизации: один флаг вместо отдельного метода

Отдельного метода нет, запрос собирается как для v2/v3:

  1. метод userrecaptcha;
  2. googlekey и pageurl со страницы;
  3. флаг enterprise: 1;
  4. результат — опросом res.php, поле g-recaptcha-response.

Python

import requests
import time

API_KEY = "YOUR_API_KEY"

# Enterprise is solved with the same method
# The solver handles the Enterprise variant automatically
submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": "6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
    "pageurl": "https://enterprise-site.com/login",
    "enterprise": 1,  # Flag for Enterprise variant
    "json": 1,
})

task_id = submit.json()["request"]

for _ in range(60):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY,
        "action": "get",
        "id": task_id,
        "json": 1,
    }).json()

    if result.get("status") == 1:
        token = result["request"]
        print(f"Enterprise token: {token[:50]}...")
        break

Интервал 5 с и лимит попыток снимают бесконечное ожидание при обрыве.

Node.js

const axios = require("axios");

async function solveEnterprise(sitekey, pageurl) {
    const API_KEY = "YOUR_API_KEY";

    const { data: submit } = await axios.post(
        "https://ocr.captchaai.com/in.php",
        new URLSearchParams({
            key: API_KEY,
            method: "userrecaptcha",
            googlekey: sitekey,
            pageurl: pageurl,
            enterprise: 1,
            json: 1,
        })
    );

    const taskId = submit.request;

    for (let i = 0; i < 60; i++) {
        await new Promise(r => setTimeout(r, 5000));
        const { data: result } = await axios.get(
            "https://ocr.captchaai.com/res.php",
            { params: { key: API_KEY, action: "get", id: taskId, json: 1 } }
        );

        if (result.status === 1) return result.request;
    }

    throw new Error("Timeout");
}

Быстрая проверка версии перед отправкой

Дешевле классифицировать страницу заранее, чем разбирать отказы.

def identify_recaptcha_version(html):
    """Determine which reCAPTCHA version a page uses."""
    if "recaptcha/enterprise.js" in html:
        return "enterprise"
    elif "recaptcha/api.js?render=" in html:
        return "v3"
    elif "g-recaptcha" in html and 'data-size="invisible"' in html:
        return "v2_invisible"
    elif "g-recaptcha" in html:
        return "v2"
    else:
        return "none"

Типичный сценарий команд в Алматы, Минске или Тбилиси: на QA-стенде магазина вход закрыт reCAPTCHA Enterprise, и регрессионные прогоны падают до бизнес-логики. Тарификация CaptchaAI идёт по потокам, а не за решение, поэтому прогон из 400 проверок и из 40 стоят одинаково: BASIC ($15/мес, 5 потоков) закрывает небольшой набор тестов, STANDARD ($30/мес, 15 потоков) — параллельные сценарии.

Про 152-ФЗ «О персональных данных» помните отдельно: берите синтетические учётные данные.


Диагностика: пять типовых отказов

Симптом Вероятная причина Что сделать
Токен отклонён Assessment API для Enterprise-сайта ушёл обычный запрос добавьте enterprise=1
Балл стабильно 0,1 при валидном токене не совпал action сверьте action с исходником страницы
SITE_MISMATCH в причинах токен выпущен для другого домена приведите pageurl к целевой странице
AUTOMATION в причинах оценки сигналы автоматизации в окружении как правило, обрабатывается на стороне CaptchaAI; при повторении — в поддержку
Токен валиден, но доступ закрыт есть проверки помимо CAPTCHA ищите другие уровни защиты (WAF, серверные правила)

По покрытию: reCAPTCHA v2 и v3 с Enterprise-вариантами, Cloudflare Turnstile и Challenge, GeeTest v3, image/OCR, grid и BLS; CaptchaFox (beta), Friendly Captcha (beta), Lemin (beta) — в бете; hCaptcha и FunCaptcha не поддерживаются, GeeTest v4 — «скоро».


Часто задаваемые вопросы

Нужно ли менять код, если сайт перешёл с v3 на Enterprise?

Достаточно добавить enterprise: 1. Метод userrecaptcha, googlekey, pageurl, поле g-recaptcha-response и опрос res.php не меняются.

Почему причины оценки не видны в ответе сервиса решения?

Причины Assessment API отдаёт владельцу сайта, а не браузеру. Сервис воспроизводит клиентскую часть и возвращает токен; riskAnalysis.reasons видны лишь в проекте Google Cloud.

Что означает action и почему из-за него падает балл?

action — метка сценария (LOGIN, CHECKOUT) из grecaptcha.enterprise.execute(). Если сервер ждёт одно значение, а токен выпущен с другим, оценка занижается независимо от качества токена. Вычитывайте значение из исходника.

Что делать, если проверка появляется на экране WAF, а не на форме?

В pageurl передавайте адрес экрана с виджетом: промежуточная страница Cloudflare или F5 — отдельный URL.

Сколько потоков нужно для регулярных прогонов по Enterprise-страницам?

Считайте от параллелизма: один поток — одна задача. Пяти потоков в BASIC ($15/мес) хватает небольшому набору тестов, 50 потоков в ADVANCE ($90/мес) — параллельному CI.


Коротко

Assessment API — серверная надстройка над знакомой reCAPTCHA: анализ рисков, причины оценки, метки Account Defender, WAF. Для автоматизации меняется немногое: определить вариант по recaptcha/enterprise.js, передать корректный action, указать верный pageurl и добавить enterprise=1 в запрос к CaptchaAI.

Похожие статьи

Комментарии для этой статьи отключены.