Подключение капчи
Scheel Captcha — страница проверки «человек или бот» на базе DDoS-Guard dCAPTCHA. Её можно встроить в формы своего сайта или поставить перед целым сайтом как шлюз.
Обзор
Есть два способа подключения:
- Виджет в форме. Вы вставляете виджет dCAPTCHA в свою форму, а ваш бэкенд проверяет токен через DDoS-Guard. Шаги 1–4.
- Шлюз. nginx перед вашим сайтом отправляет посетителя без пропуска на
https://captcha.scheel.ru/. После прохождения капчи сервис ставит cookie-пропуск и возвращает пользователя назад. См. Режим шлюза.
Как проходит проверка:
- Браузер загружает виджет с
captcha.ddos-guard.netпо Site key. - Пользователь проходит проверку, виджет выдаёт токен
ddg-captcha-token. - Сервер отправляет токен вместе с Private key на
https://captcha.ddos-guard.net/siteverifyи получаетsuccess: true/false.
1. Получить ключи
- В личном кабинете DDoS-Guard: + Добавить услугу → dCAPTCHA, выберите тариф.
- Откройте страницу Ключи и нажмите Добавить ключ. Заполните название и список доменов.
- В домены добавьте
captcha.scheel.ruи домены сайтов, где будет стоять виджет.*.example.comпокрывает только один уровень поддоменов:a.example.comподходит,a.b.example.comнет. - После сохранения откроется страница конфигурации ключа с 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 Truetimeout-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 сервиса:
COOKIE_DOMAIN=.scheel.ru— cookie-пропускscheel_captcha_passбудет виден всем поддоменам.ALLOWED_REDIRECT_HOSTSсодержит защищаемый домен, например*.scheel.ru. На другие доменыreturnне сработает (защита от open redirect).SESSION_TTL_SECONDS— срок действия пропуска.
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-secret | Private key неверный или ключ выключен. |
missing-input-response | Не передан токен. |
invalid-input-response | Токен неверный. |
timeout-or-duplicate | Токен устарел или уже был проверен. |
bad-request | Некорректный запрос. |
upstream-unavailable | Только в этом сервисе: DDoS-Guard не ответил за отведённое время. |