Scheel Captcha

Подключение капчи

Scheel Captcha — страница проверки «человек или бот» на базе DDoS-Guard dCAPTCHA. Её можно встроить в формы своего сайта или поставить перед целым сайтом как шлюз.

Обзор

Есть два способа подключения:

Как проходит проверка:

  1. Браузер загружает виджет с captcha.ddos-guard.net по Site key.
  2. Пользователь проходит проверку, виджет выдаёт токен ddg-captcha-token.
  3. Сервер отправляет токен вместе с Private key на https://captcha.ddos-guard.net/siteverify и получает success: true/false.

1. Получить ключи

  1. В личном кабинете DDoS-Guard: + Добавить услугу → dCAPTCHA, выберите тариф.
  2. Откройте страницу Ключи и нажмите Добавить ключ. Заполните название и список доменов.
  3. В домены добавьте captcha.scheel.ru и домены сайтов, где будет стоять виджет. *.example.com покрывает только один уровень поддоменов: a.example.com подходит, a.b.example.com нет.
  4. После сохранения откроется страница конфигурации ключа с Site key и Private key.
Site key публичный, его видит браузер. Private key секретный: храните его только на сервере и не отдавайте во фронтенд.

2. Указать ключи в сервисе

Ключи задаются в файле /opt/captcha/.env на сервере, где запущен контейнер:

DDG_SITE_KEY=ваш_site_key
DDG_PRIVATE_KEY=ваш_private_key

После изменения перезапустите контейнер:

cd /opt/captcha && docker compose up -d

Проверка: GET /healthz должен вернуть "keysConfigured": true.

3. Виджет на своём сайте

Автоматический рендер

Подключите скрипт и поставьте контейнер внутри формы. После прохождения в форму добавится скрытое поле ddg-captcha-token.

<script src="https://captcha.ddos-guard.net/static/api.js" defer></script>

<form action="/login" method="post">
  <input name="email" type="email" required>
  <div class="ddg-captcha-container" data-sitekey="ВАШ_SITE_KEY"></div>
  <button type="submit">Войти</button>
</form>

Чтобы получить токен в JS, добавьте data-callback="handleCaptchaToken" и объявите глобальную функцию handleCaptchaToken(token).

Явный рендер (SPA)

<div id="captcha-slot"></div>
<script>
  window.initCaptcha = () => {
    window.ddgcaptcha.ready(() => {
      window.ddgcaptcha.render("captcha-slot", {
        sitekey: "ВАШ_SITE_KEY",
        callback: (token) => submitForm(token),
      });
    });
  };
</script>
<script src="https://captcha.ddos-guard.net/static/api.js?render=explicit&onload=initCaptcha" defer></script>
МетодЧто делает
render(container, params, inherit?)Рисует виджет, возвращает widgetId. При inherit = true недостающие параметры берутся из data-атрибутов контейнера.
getResponse(widgetId?)Текущий токен или пустая строка.
reset(widgetId?)Сбрасывает виджет. Вызывайте после неудачной проверки на сервере.
ready(fn)Вызывает fn, когда API загружено.

Без widgetId методы работают с первым виджетом на странице.

4. Проверка токена на сервере

POST https://captcha.ddos-guard.net/siteverify, Content-Type: application/json. Тело: response (токен из формы) и private_key.

curl

curl -sX POST https://captcha.ddos-guard.net/siteverify \
  -H 'Content-Type: application/json' \
  -d '{"response":"ТОКЕН","private_key":"ВАШ_PRIVATE_KEY"}'

Ответ:

{
  "success": true,
  "challenge_ts": "2026-10-23T12:34:56Z",
  "hostname": "example.com",
  "error-codes": []
}

Node.js (18+)

async function verifyCaptcha(token) {
  const res = await fetch("https://captcha.ddos-guard.net/siteverify", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ response: token, private_key: process.env.DDG_PRIVATE_KEY }),
    signal: AbortSignal.timeout(5000),
  });
  const data = await res.json();
  return data.success === true;
}

// Express
app.post("/login", express.urlencoded({ extended: false }), async (req, res) => {
  if (!(await verifyCaptcha(req.body["ddg-captcha-token"]))) {
    return res.status(403).send("Капча не пройдена");
  }
  // ...
});

PHP

<?php
$ch = curl_init('https://captcha.ddos-guard.net/siteverify');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 5,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode([
        'response'    => $_POST['ddg-captcha-token'] ?? '',
        'private_key' => getenv('DDG_PRIVATE_KEY'),
    ]),
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);

if (empty($data['success'])) {
    http_response_code(403);
    exit('Капча не пройдена');
}

Python

import os, requests

def verify_captcha(token: str) -> bool:
    r = requests.post(
        "https://captcha.ddos-guard.net/siteverify",
        json={"response": token, "private_key": os.environ["DDG_PRIVATE_KEY"]},
        timeout=5,
    )
    return r.json().get("success") is True
Токен одноразовый и живёт ограниченное время. Повторная проверка того же токена вернёт timeout-or-duplicate.

Режим шлюза (nginx)

Закрывает целый сайт на поддомене scheel.ru без изменений в его коде. nginx спрашивает у сервиса, есть ли у посетителя пропуск. Если нет — отправляет на страницу проверки с параметром return.

server {
    server_name app.scheel.ru;

    location = /__captcha_check {
        internal;
        proxy_pass https://captcha.scheel.ru/api/check;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header Cookie $http_cookie;
    }

    location / {
        auth_request /__captcha_check;
        error_page 401 = @captcha;
        proxy_pass http://app_backend;
    }

    location @captcha {
        return 302 https://captcha.scheel.ru/?return=$scheme://$host$request_uri;
    }
}

Условия для шлюза в .env сервиса:

API сервиса

ЗапросОписание
GET /?return=URLСтраница проверки. После прохождения вернёт на URL, если домен разрешён.
POST /api/verifyТело {"token": "...", "return": "URL"}. Проверяет токен через DDoS-Guard, ставит cookie-пропуск. Ответ 200 {"success": true, "redirect": "URL" | null} или 403 {"success": false, "errorCodes": [...]}.
GET /api/checkДля nginx auth_request: 204 — пропуск действителен, 401 — нет.
GET /healthz{"status": "ok", "keysConfigured": true}

Коды ошибок

КодЗначение
missing-input-secretНе передан Private key.
invalid-input-secretPrivate key неверный или ключ выключен.
missing-input-responseНе передан токен.
invalid-input-responseТокен неверный.
timeout-or-duplicateТокен устарел или уже был проверен.
bad-requestНекорректный запрос.
upstream-unavailableТолько в этом сервисе: DDoS-Guard не ответил за отведённое время.