Как выдавать API-ключи капчи вашим клиентам

provision captcha api keys - How to Provision CAPTCHA API Keys for Your Customers

Если вы держите CapSkip для других людей, API-ключи капчи можно выдавать прямо в момент оплаты, из вашего же биллингового webhook. Remote Key Management открывает три POST-эндпоинта, которые добавляют, перечисляют и удаляют ключи на работающем экземпляре, а новый ключ действует уже со следующего запроса на решение капчи. Ни перезапуска, ни ручного шага: между прошедшим платежом и клиентом, который начал решать капчи, больше ничего не стоит. В статье разбираем, как выдать ключ при подписке, как отозвать его при отмене, как сверять список ключей с биллингом и какую сетевую ошибку такая схема провоцирует.

Что понадобится

  • CapSkip работает на машине с Windows, которой распоряжаетесь вы. Эта машина и есть ваш сервис распознавания, и она единственная, где приложение вообще нужно установить. Вашим клиентам ставить не нужно ничего.
  • Режим Server, чтобы клиенты действительно могли до неё достучаться. Режим Local слушает петлевой адрес и обслуживает только само устройство, а режим Server слушает ваш внутренний или публичный IP, так что подключиться могут и другие машины. Оба режима описаны в разделе Настройки подключения, и прежде чем давать кому-то адрес, стоит обзавестись статическим публичным IP.
  • Один заход в окно CapSkip, чтобы сгенерировать админский token. Это единственный шаг, который происходит в интерфейсе, и повторять его не придётся.
  • Место, откуда вы будете делать эти вызовы: обработчик биллингового webhook, бэкенд вашей панели или терминал, пока вы всё это проверяете.

Прежде чем перейти к коду, стоит сказать прямо, потому что именно на этом вся схема и держится. Ключи, которые вы раздаёте, вы чеканите сами. Это не купленные вами кредиты, которые вы перепродаёте, и за ними не стоит никакой поштучной тарификации. CapSkip работает на вашем железе, поэтому сотня клиентских ключей стоит ровно столько же, сколько один.

Шаг 1: включите Remote Key Management

Переключатель находится в Settings, в разделе API Key Validation, далее Advanced, затем Remote Key Management. Включите его и нажмите кнопку Generate.

Token показывается один раз и хранится только в виде хеша с солью, поэтому прочитать его потом уже негде. Потеряли: генерируйте новый, старый перестанет работать немедленно. Считайте это кнопкой отзыва доступа, потому что отдельной такой кнопки нет. Держите его там же, где ваш биллинг держит остальные секреты, а не в коде приложения.

Эндпоинты живут на том же хосте и порту, который уже отвечает на ваши запросы распознавания. Если клиенты обращаются к порту 8080, там же находится и админский API. Это удобно, и ровно с этим же надо быть осторожным: об этом отдельный раздел ниже.

Пока функция выключена, любой админский путь отдаёт голый 404 без тела ответа. Ровно то же самое приходит в ответ на несуществующий путь, и сделано это намеренно: тот, кто сканирует порт, не отличит экземпляр с выключенной функцией от экземпляра, который о ней вообще не знает.

Шаг 2: выдайте ключ, когда клиент оформил подписку

Все три эндпоинта работают по POST, принимают тело в JSON и требуют заголовок Authorization с token в виде учётных данных Bearer. Вызову добавления нужно имя. Передайте только имя, а значение ключа сгенерирует CapSkip: здесь это именно то, что нужно, ведь клиент не должен выбирать себе учётные данные сам.

# No install needed. Name the key after the customer, not after the plan.
curl -X POST http://YOUR_SERVER:8080/admin/keys/add \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" -d '{"name":"cust_10482"}'

# Response carries the value to show the customer once:
# {"errorId":0,"key":{"name":"cust_10482","key":"..."}}

Называйте ключ по чему-то устойчивому в вашей же системе: по идентификатору клиента или подписки. Имена уникальны, и правило проверяется при добавлении, поэтому имя становится надёжной ручкой для всех последующих операций. А вот названия по тарифу или по дате как раз и превращают день отмены в мучение.

Это же правило уникальности служит вам защитой от повторов. Платёжные сервисы пересылают webhook повторно, и повторное событие о подписке вернёт 409 и ERROR_KEY_EXISTS вместо того, чтобы тихо завести второй ключ тому же клиенту. Считайте такой 409 успехом в обработчике, и проблема повторов исчезнет.

Значение возвращается ровно один раз, в этом самом ответе. Покажите его клиенту или сохраните туда, откуда читает ваша панель: получение списка ключей потом относится к админским операциям, и запускать его ради восстановления одних учётных данных вам точно не захочется.

Шаг 3: отзовите ключ при отмене подписки

Удаление принимает ровно один признак: либо значение ключа, либо имя. Раз вы называли ключи по клиентам, берите имя:

# By name, which is unambiguous because names are unique.
curl -X POST http://YOUR_SERVER:8080/admin/keys/delete \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" -d '{"name":"cust_10482"}'

# By value works too, if that is what your records hold:
#   -d '{"key":"the-old-value"}'

Отзыв вступает в силу со следующего запроса распознавания, так что доступ пропадает в момент возврата вызова. С этой секунды вызовы клиента будут завершаться ошибкой ERROR_KEY_DOES_NOT_EXIST, и это достаточно внятный сигнал: их интеграция может показать его как истёкшую подписку, а не как аварию на вашей стороне.

Момент отзыва выбирайте осознанно. Удаление по событию отмены обрывает доступ сразу, хотя клиент обычно оплатил до конца периода. Удаление по событию окончания периода и есть то, чего в большинстве случаев хотят на самом деле. Неудачное списание представляет собой третий случай: небольшая отсрочка перед отзывом оборачивается куда меньшим числом обращений в поддержку, чем ключ, исчезнувший при повторной попытке списания.

Шаг 4: сверяйте список с вашей биллинговой системой

Вызов со списком принимает пустой объект и возвращает каждый ключ, который экземпляр принимает прямо сейчас.

# The audit: exactly who can solve right now.
curl -X POST http://YOUR_SERVER:8080/admin/keys/list \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" -d '{}'
{
  "errorId": 0,
  "keys": [
    { "name": "cust_10482", "key": "..." },
    { "name": "cust_10515", "key": "..." }
  ]
}

Значение errorId, равное 0, означает успех, и это та же конвенция, что и у эндпоинтов распознавания. Запускайте это по расписанию и сверяйте имена с вашими активными подписками. Ключ без подписки означает, что кто-то до сих пор решает капчи бесплатно, а подписка без ключа означает клиента, чей webhook на выдачу потерялся и который вот-вот напишет в поддержку. Обе ситуации молчат, пока вы не посмотрите, и этот вызов остаётся единственным местом, где живёт правда.

Как встроить это в webhook подписки

Всё вместе укладывается в два обработчика и одно правило идемпотентности:

# pip install requests
import requests

BASE = "http://YOUR_SERVER:8080"
AUTH = {"Authorization": "Bearer YOUR_TOKEN"}

def admin(path, body):
    r = requests.post(f"{BASE}/admin/keys/{path}", json=body,
                      headers=AUTH, timeout=10)
    # 409 on add means the key already exists, which is what a
    # retried webhook looks like. Treat it as success, not failure.
    if r.status_code == 409:
        return None
    r.raise_for_status()
    return r.json()

def on_subscription_active(customer_id):
    created = admin("add", {"name": f"cust_{customer_id}"})
    return created["key"]["key"] if created else None

def on_subscription_ended(customer_id):
    admin("delete", {"name": f"cust_{customer_id}"})

К значению None от повторного события о подписке отнеситесь внимательно. Оно означает, что ключ есть, но его значение вам больше не увидеть, поэтому если в первый раз вы его не сохранили, восстанавливаться надо удалением и повторным созданием, а не получением списка. Сохраните значение сразу при первом получении, и ситуация просто не возникнет.

Не оставляйте админский API на порту, доступном клиентам

Для такой схемы это самый важный раздел, потому что удобство одного хоста и порта работает в обе стороны. Вашим клиентам нужен доступ к эндпоинтам распознавания. Админские эндпоинты находятся на том же порту, защищены только Bearer token, а проверки транспорта нет вовсе, так что на нестандартном порту этот token идёт открытым HTTP. Любой, кто способен наблюдать трафик, прочитает его, а любой, у кого он окажется, выпишет себе ключ.

Поставьте перед экземпляром обратный прокси и разведите две аудитории:

  • В интернет публикуйте только пути распознавания, по HTTPS с терминацией на прокси, а на всё, что лежит под админским путём, отдавайте 404.
  • К админским эндпоинтам ходите со своего бэкенда по закрытому маршруту: через петлевой адрес, если биллинг работает на той же машине с Windows, иначе через частную сеть, VPN или SSH-туннель.

Правило простое: админский token не должен покидать вашу инфраструктуру, а порт, с которым он разговаривает, не должен быть тем, до которого дотягивается клиент. Никогда не выставляйте админский путь в открытый интернет, независимо от того, включена функция или нет.

Стоит знать ещё одну границу, ведь клиентская панель как раз и есть первое, что здесь хочется построить. Браузеры вызвать эти эндпоинты не смогут, потому что админские ответы по замыслу не несут заголовков CORS . Ваша панель обязана ходить через ваш собственный бэкенд, где token и должен лежать.

Куда именно записываются ключи

Ключи попадают туда, где ваши ключи уже лежат. Direct Input и From File представляют собой два отдельных списка, и API читает и пишет тот из них, который сейчас выбран. Переключение источника в окне настроек меняет и то, что видят эндпоинты, поэтому убедитесь, в каком вы режиме, прежде чем недоумевать, почему только что выданного ключа нет в списке.

У файлового режима есть одна особенность, о которой лучше знать заранее: первая же запись переписывает текстовый файл ключей в формате JSON. Ничего не теряется, но формат меняется навсегда, поэтому сделайте резервную копию, если файл читает что-то ещё. А если файл вообще не выбран, записи некуда лечь, и вы получите ERROR_ADMIN_STORE_FAILURE с кодом 500. Практически всегда 500 здесь означает именно это.

Списки, созданные до появления этой функции, всё ещё могут содержать повторяющиеся имена, потому что уникальность проверяется только при добавлении. Удаление по имени в таком случае вернёт ERROR_KEY_NAME_AMBIGUOUS и ничего не изменит. Удалите по значению, и неоднозначность исчезнет.

Кавычки в оболочках Windows, если вы проверяете прямо на этой машине

CapSkip представляет собой приложение для Windows, поэтому машина, с которой вы всё проверяете, тоже обычно под Windows. Командная строка не считает одинарную кавычку кавычкой. Скопированная команда отправляет сами кавычки как часть тела JSON, парсер их отвергает, и вы получаете ERROR_ADMIN_BAD_REQUEST на команде, которая выглядит совершенно правильной.

ОболочкаПустое телоС полями
Командная строка-d "{}"-d "{\"name\":\"prod\"}"
PowerShell, через curl.exe-d '{}'-d '{\"name\":\"prod\"}'
Git Bash, macOS, Linux-d '{}'-d '{"name":"prod"}'

В PowerShell вызывайте curl.exe полным именем. Обычный curl там представляет собой псевдоним для Invoke-WebRequest, у которого совсем другие аргументы, и ошибка будет никак не связана с этим API.

Коды ошибок

СтатусКодПричина
404простой текстФункция выключена либо путь неизвестен. Специально сделано неразличимым.
401ERROR_ADMIN_UNAUTHORIZEDToken отсутствует, повреждён или неверен.
405ERROR_ADMIN_METHOD_NOT_ALLOWEDОтправлен не POST.
400ERROR_ADMIN_BAD_REQUESTТело не является корректным JSON, у добавления нет имени, либо у удаления заданы оба признака или ни одного.
409ERROR_KEY_EXISTSТакое имя или такое значение уже используется. В обработчике подписки это обычно повторно присланный webhook.
409ERROR_KEY_NAME_AMBIGUOUSУдаление по имени совпало с несколькими записями. Удаляйте по значению.
404ERROR_KEY_NOT_FOUNDУдаление ни с чем не совпало. Обычно это уже обработанная отмена.
500ERROR_ADMIN_STORE_FAILUREНе удалось сохранить. Обычно это файловый режим без выбранного файла.

Эти коды соседствуют с кодами ошибок распознавания, а не заменяют их, и полный набор приведён в разделе Документация по API CapSkip.

FAQ

Как быстро новый ключ реально начинает работать?

Со следующего запроса распознавания. Ничего не кэшируется и ничего не требует перезагрузки, поэтому клиент, получивший ключ на странице оплаты, воспользуется им в ту же минуту. Отзыв в обратную сторону такой же мгновенный, и именно поэтому момент срабатывания вашего обработчика отмены остаётся вопросом политики, а не техники.

Стоит ли денег каждый следующий клиентский ключ?

Нет. CapSkip работает на вашем собственном железе без поштучных квот, поэтому ключ представляет собой метку вызывающей стороны, а не платёжную личность. Заводите по одному на клиента или по нескольку, если хотите развести его окружения, и удаляйте их столь же свободно. Компонент SDK для распознавания капчи использует тот ключ, который вы ему передали, так что схему разделения вы придумываете сами.

Могу ли я через этот API считать трафик клиента или ограничивать его?

Через этот API нет. Он добавляет, перечисляет и удаляет ключи, и никакая другая настройка через него не читается и не меняется. Если ваши тарифы различаются по объёму, считайте запросы на том самом обратном прокси, который вы и так ставите перед экземпляром, опираясь на API-ключ, который прислал клиент. Ключ определяет вызывающую сторону, а прокси решает, что этой стороне позволено.

Я потерял админский token. Что теперь?

Сгенерируйте новый в той же панели настроек. Старый token перестаёт работать немедленно, поэтому никакой уборки не требуется и нет окна, в котором действуют оба. Клиентов это не затрагивает: их ключи остаются нетронутыми, и они продолжают решать капчи. Ломается только ваша собственная выдача ключей, пока вы не обновите секрет, который читает обработчик webhook.

Самая короткая версия

Remote Key Management включается один раз, а token храните вместе с остальными биллинговыми секретами. Когда подписка становится активной, добавляйте ключ с именем клиента, повторный 409 считайте успехом, а по окончании подписки удаляйте по тому же имени. Запрашивайте список по расписанию и сверяйте его с активными подписчиками, потому что только там живёт настоящий ответ. Затем поставьте впереди прокси и уберите админский путь с порта, который видят клиенты. Сделайте это, и обход капчи превратится в то, что вы можете перепродавать на собственном железе, а скорость выдачи будет равна скорости, с которой ваша касса отправляет webhook.