S Subster

Аутентификация

Вы выпустите ключ доступа и подпишете им запрос с вашего сервера — минут десять, если кабинет уже открыт, и дольше, если ключ придётся просить у владельца проекта.

Перед началом

Ключи лежат в кабинете: Настройки проекта → Подключение приложения → Для разработчика, секция «Ключи доступа». Там же, в поле «Адрес API», лежит основа всех адресов ниже: https://api.subster.ai.

Шаг 1. Выпустите ключ доступа

Нажмите «Создать ключ», дайте ему имя и выберите режим — боевой (sk_live_…) или тестовый (sk_test_…). Значение показывается один раз: не сохранили — выпускайте новый.

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

Это ключ Subster, а не Stripe

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

Шаг 2. Подпишите запрос ключом

curl "https://api.subster.ai/s2s/v1/entitlement?guid=<GUID>" \
  -H "Authorization: Bearer sk_live_…"

<GUID> — идентификатор покупателя: его приложение получает при опознании и передаёт вашему серверу.

Тем же заголовком открываются все серверные запросы — адреса /s2s/v1/*. Номер проекта мы берём из самого ключа, передавать его отдельно не нужно. Держите ключ на сервере: в коде приложения ему не место.

Что может ключ

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

ПравоЗапросыЕсть у нового ключа
entitlement:readGET /s2s/v1/entitlementда
users:writePOST /s2s/v1/usersда
identity:readGET /s2s/v1/identity/link-profile, POST /s2s/v1/identity/resolve-by-email, POST /public/identity/resolve-by-email с ключомда
identity:writePOST /s2s/v1/identity/link-profileда
user:readGET /s2s/v1/user/propertiesда
user:writePOST /s2s/v1/user/propertiesда
events:writePOST /public/event с ключомда
subscription:managePOST /s2s/v1/subscription/cancel, /pause, /resume, /portalпосле переключателя
billing:chargePOST /s2s/v1/subscription/charge, GET /s2s/v1/subscription/charge/statusпосле переключателя
billing:refundPOST /s2s/v1/subscription/refundпосле переключателя

Выбрать права при выпуске кабинет не даёт: окно «Создать ключ» спрашивает только имя и режим.

Подпись секретом — для двух публичных адресов

POST /public/event и POST /public/identity/resolve-by-email принимают и ключ, и подпись тела. Начните с ключа — он короче. Подпись берите, если ключ на этом участке держать негде.

С ключом:

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

  1. Возьмите текущее время в миллисекундах — это timestamp.
  2. Соберите строку ${timestamp}.${rawBody}: тело берётся байт в байт, как уходит в сеть, без повторной сборки JSON.
  3. Посчитайте от неё HMAC-SHA256 секретом проекта, результат переведите в hex.
  4. Отправьте два заголовка: X-Signature: sha256=<hex> и X-Signature-Timestamp с тем же временем.
const ts = String(Date.now());
const sig = crypto.createHmac("sha256", PROJECT_SECRET)
  .update(`${ts}.${rawBody}`).digest("hex");
// headers: { "X-Signature": `sha256=${sig}`, "X-Signature-Timestamp": ts }

В подписанном теле должно быть поле projectId — по нему мы выбираем, чьим секретом проверять подпись. Значение лежит в поле «ID проекта» на той же вкладке «Для разработчика». Время сверяется с нашим: разошлись больше чем на пять минут — отказ.

Метка здесь в миллисекундах, а в подписи наших событий к вам — в секундах: код проверки с одной стороны на другую не переносится (Вебхуки).

Что приходит при отказе

Серверные запросы отказывают телом { "error": { "type", "code", "message" } }. Ветвитесь по code: текст message может измениться.

ОтветtypeЧто случилось
401authentication_errorключа нет, он испорчен, отозван или это старый ключ после ротации
403permission_errorоперация закрыта — причину называет code, таблица ниже
400, 409, 422invalid_request_errorошибка в самом запросе, в том числе параметр, которого запрос не знает, или конфликт — например, платёж уже возвращён
429rate_limit_errorзапросов больше потолка — таблица ниже
404not_foundресурса нет либо он принадлежит другому проекту — ответ один на оба случая
5xxapi_errorсбой на нашей стороне, code тоже api_error — повторите позже

Что значит code у отказа 403:

codeПричинаЧто делать
permission_errorу ключа нет права на эту операциюсверить с таблицей прав выше
charge_disabled, refund_disabled, subscription_manage_disabledвладелец проекта не включил переключатель списаний, возвратов или управления подпискойпопросить владельца включить — «Деньги из приложения»
test_key_not_allowedсписание или возврат тестовым ключомвыпустить боевой ключ
bridge_tier_expiredплатный тариф проекта не оплачен или сменён на бесплатный, и льготный срок кончилсявладельцу вернуть платный тариф; ключи перевыпускать не нужно
bridge_temporarily_unavailableсбой на нашей сторонеповторить позже, с тарифом всё в порядке
project_frozen, project_readonlyденьги и управление подпиской остановлены на уровне организации или проектаразбор в «Диагностике»

Два публичных адреса выше устроены иначе: неизвестный ключ и несошедшаяся подпись дают там 404 без подробностей. 401 оттуда не приходит никогда.

Сколько запросов можно

Сверх потолка приходит 429. Счётчик у каждого адреса свой: сотня проверок права доступа не отнимает потолок у записи свойств.

Серверные запросы считаются по ключу. Все ваши серверы с одним ключом делят один счётчик, и смена IP-адреса его не обнуляет. Открытые адреса считаются по IP-адресу отправителя, даже если запрос несёт ключ. Исключение — приём событий: он считает события проекта.

АдресПотолок
каждый адрес /s2s/v1/*120 в минуту на ключ
GET /public/entitlement60 в минуту с IP-адреса
POST /public/event300 событий в минуту на проект; с одного IP-адреса — до 3000 любых запросов и до 60 не прошедших проверку ключа или подписи
POST /public/identity/resolve-by-email60 в минуту с IP-адреса
POST /public/handoff/app-callback60 в минуту с IP-адреса
GET /public/handoff/resolve10 в минуту с IP-адреса
POST /public/handoff/resolve-by-fingerprint10 в минуту с IP-адреса
POST /public/handoff/email-recovery/request5 за 15 минут с IP-адреса; письмо на одну почту — не чаще раза в 20 минут
POST /public/handoff/portal-entry/request5 за 15 минут с IP-адреса; письмо на одну почту — не чаще раза в 20 минут

Ротация и отзыв

Для ключа доступа оба действия лежат в меню «⋯» в конце его строки. «Ротировать» выпускает новый ключ, а старый принимается ещё 72 часа — успеете выкатить сервер без простоя. Потолок частоты у нового ключа свой, со старым он не общий. «Отозвать» гасит ключ сразу и навсегда. У старого ключа с меткой «дожитие до …» в меню остаётся одно «Отозвать сейчас».

Секрет подписи меняется кнопкой «Ротировать» в его строке. Старый секрет принимается ещё 48 часов.

Дальше