NeuroCaptcha

Вебхуки

Результат без опроса: подписанный POST на ваш callbackUrl, когда задача завершена, с тремя попытками доставки.

На этой странице

Передайте callbackUrl в createTask (или pingback в in.php). Когда задача завершится — решена или нет, — мы отправим на этот адрес POST с JSON. getTaskResult при этом тоже работает.

createTask
{
  "clientKey": "YOUR_API_KEY",
  "task": {
    "type": "RecaptchaV2TaskProxyless",
    "websiteURL": "https://www.google.com/recaptcha/api2/demo",
    "websiteKey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
  },
  "callbackUrl": "https://example.com/captcha-webhook"
}

Что мы отправляем#

Запрос вебхука
POST /captcha-webhook HTTP/1.1
Host: example.com
Content-Type: application/json
User-Agent: captcha-api-webhooks/1
X-Task-Id: 7781
X-Signature: t=1793370008,v1=6c1f0e2b9d4a…

{"taskId":7781,"errorId":0,"status":"ready","solution":{"gRecaptchaResponse":"03AFcWeA6Z3vq0VhGm1oS8r0bAxKk4…","userAgent":""},"cost":"0.000600","createTime":1793370000,"endTime":1793370007,"solveCount":1}

Тело — ответ getTaskResult плюс taskId (без ip). Неудачная задача:

Неудачная задача
{"taskId":7782,"errorId":12,"errorCode":"ERROR_CAPTCHA_UNSOLVABLE","errorDescription":"the task could not be solved"}

Доставка и повторы#

  • Ответьте любым статусом 2xx в течение 5 секунд; тело ответа не читается.
  • До 3 попыток: сразу, через 2 секунды и ещё через 6 секунд. Токен reCAPTCHA живёт около 2 минут, поэтому поздние повторы бессмысленны — если нужна гарантия, оставьте опрос как запасной путь.
  • Редиректы не выполняются. Адрес должен быть http(s) и указывать на публичный IP.
  • Доставка может повториться (например, если ваш ответ потерялся) — убирайте дубли по taskId.
  • Вебхуки test-ключей доставляются и подписываются так же.

Подпись#

Каждый запрос подписан секретом вебхуков вашего аккаунта (whsec_…). Он находится в Кабинете → Уведомления; там же его можно перевыпустить — новый секрет действует сразу.

Заголовок
X-Signature: t=<unix-время>,v1=<hex HMAC-SHA256(secret, "<t>.<сырое тело>")>
  1. Разбейте заголовок по , и возьмите t и v1.
  2. Посчитайте HMAC-SHA256 с секретом в качестве ключа от строки: t, точка и сырое тело — байты в том виде, в каком они пришли, до разбора JSON.
  3. Сравните hex-результат с v1 за постоянное время.
  4. Отклоняйте запросы, у которых t отличается от ваших часов больше чем на 5 минут, — это защита от повторной отправки.

Примеры проверки#

import hashlib
import hmac
import json
import os
import time

from flask import Flask, abort, request

SECRET = os.environ["WEBHOOK_SECRET"]  # whsec_… from the dashboard
TOLERANCE = 300  # seconds


def verify(header: str, body: bytes) -> bool:
    try:
        parts = dict(item.split("=", 1) for item in header.split(","))
        t, signature = int(parts["t"]), parts["v1"]
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > TOLERANCE:
        return False
    expected = hmac.new(SECRET.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)


app = Flask(__name__)


@app.post("/captcha-webhook")
def captcha_webhook():
    body = request.get_data()  # raw bytes, exactly as sent
    if not verify(request.headers.get("X-Signature", ""), body):
        abort(401)
    event = json.loads(body)
    if event["errorId"] == 0:
        print(event["taskId"], event["solution"]["gRecaptchaResponse"])
    else:
        print(event["taskId"], "failed:", event["errorCode"])
    return "", 204

Проверка#

Создайте задачу с test-ключом и callbackUrl на ваш обработчик: вебхук придёт через пару секунд и будет подписан так же, как боевой. Локальному серверу нужен публичный адрес — используйте туннель; частные и локальные адреса отклоняются.