Skip to Content

Ошибки

Каждый ответ 4xx/5xx несёт один и тот же JSON-конверт:

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" } ] }
  • codeчисловой HTTP-статус (400, 401, 404, …). Полезен для общей обработки на HTTP-уровне, но для логики ветвления переключайтесь по messagecode не разделит, например, invalid_input и payment_method_not_supported (оба 400).
  • message — sentinel-имя в нижнем snake_case, выведенное из внутренней константы errors.go (например, INVALID_INPUT → "invalid_input"). Стабильно между релизами — переключайтесь по нему.
  • details — заполняется при ошибках валидации. Массив объектов { field, message }, чтобы клиент мог привязать ошибки к полям ввода. В остальных случаях не указывается.

Мы сейчас не возвращаем trace_id или timestamp в теле. Ранние черновики этой страницы обещали оба — это было намерением, а не реальностью. Если вам нужно сопоставить серверный лог с запросом, снимите заголовок Date из ответа и серверные rate-limit заголовки (X-RateLimit-*) и приложите их к тикету в поддержку.

Отклонения на кромке gateway используют другую форму. Конверт выше — то, что отправляют backend-сервисы. Запросы, отклонённые на gateway до достижения сервиса — отсутствующая/некорректная X-Signature, неизвестный X-Client-ID или устаревший X-Timestamp на вызове /b2b/v1/* — приходят как { "error": "...", "message": "..." }, где error — грубый slug (unauthorized / bad_request / service_unavailable), а message несёт конкретику. Нет числового code и нет details. Поэтому проверяющая сторона должна сначала ветвиться по HTTP-статусу, потом читать message, и трактовать code/details как присутствующие только когда запрос прошёл gateway. Пример тела gateway (401):

{ "error": "unauthorized", "message": "invalid signature" }

HTTP-статус → типичные коды

HTTPТипичные значения codeЧто значит
400INVALID_INPUT, MISSING_REQUIRED, INVALID_FORMAT, INVALID_LENGTH, INVALID_VALUE, INVALID_USER_STATUS, INVALID_USER_ROLE, PAYMENT_METHOD_NOT_SUPPORTED, AMOUNT_BELOW_MINIMUMНекорректный запрос — смотрите details
401INVALID_CREDENTIALS, INVALID_TOKEN, TOKEN_EXPIRED, INVALID_SIGNATURE (B2B); INVALID_OTP, OTP_EXPIRED, SESSION_EXPIRED (только панель / JWT)Аутентификация не прошла — неверный ключ, истёкший timestamp, неправильная подпись. Коды OTP/SESSION_EXPIRED появляются только на маршрутах JWT-панели (/payment/v1/*); чистые B2B-интеграции их не увидят.
402INSUFFICIENT_CREDITПредоплаченный баланс мерчанта закончился — пополните перед повтором
403FORBIDDEN, IP_BLOCKEDКлюч валиден, но у него нет scope’а / разрешения по IP для этого вызова
404NOT_FOUND, RECORD_NOT_FOUND, USER_NOT_FOUND, SESSION_NOT_FOUNDРесурс не существует (или не существует для этого мерчанта)
409ALREADY_EXISTS, USER_ALREADY_EXISTS, SESSION_ALREADY_EXISTSIdempotency-replay с другим телом, либо state-машина отказала в переходе
429TOO_MANY_REQUESTS, TOO_MANY_ATTEMPTSДостигнут rate-limit; сделайте отступ и повторите
500INTERNAL_SERVER_ERROR, DATABASE_CONNECTION_ERROR, DATABASE_QUERY_ERROR, DATABASE_TRANSACTION_ERROR, REDIS_CONNECTION_ERROR, REDIS_OPERATION_ERROR, EXTERNAL_SERVICE_ERRORНаша вина; безопасно повторить с отступом. (Provider-специфичные коды вроде TWILIO_SERVICE_ERROR / SENDGRID_SERVICE_ERROR существуют внутри, но всплывают только на потоках уведомлений со стороны панели, не на B2B-endpoint’ах.)
503SERVICE_UNAVAILABLEDownstream-зависимость не работает. Повторите с отступом

Полный справочник кодов

Полный набор значений code, которые вы можете увидеть (совпадает с payment-service/pkg/errors/errors.go):

Аутентификация и сессии (401)

  • INVALID_CREDENTIALS — комбинация username/password или API-ключа отклонена
  • INVALID_TOKEN — JWT/сессионный токен непарсится или повреждён
  • TOKEN_EXPIRED — JWT прошёл exp
  • INVALID_OTP — OTP не совпал
  • OTP_EXPIRED — OTP был выпущен раньше окна допуска
  • SESSION_EXPIRED — сессия панели устарела
  • INVALID_SIGNATURE — несовпадение HMAC-подписи на B2B / webhook-вызовах

Авторизация (403)

  • FORBIDDEN — аутентифицирован, но роль/scope/граница мерчанта блокирует действие
  • IP_BLOCKED — IP в списке злоупотреблений

Не найдено (404)

  • NOT_FOUND — общая
  • RECORD_NOT_FOUND — строка отсутствует по указанному ID
  • USER_NOT_FOUND — пользователь не найден
  • SESSION_NOT_FOUND — id сессии панели не распознан

Конфликт (409)

  • ALREADY_EXISTS — общая
  • USER_ALREADY_EXISTS — регистрация наткнулась на unique-constraint
  • SESSION_ALREADY_EXISTS — дубль вставки сессии

Валидация (400)

  • INVALID_INPUT — общая; смотрите details
  • MISSING_REQUIRED — обязательное поле отсутствовало
  • INVALID_FORMAT — значение не соответствовало ожидаемому формату (например, UUID, URL, email)
  • INVALID_LENGTH — значение слишком короткое или слишком длинное
  • INVALID_VALUE — значение вне допустимого enum/диапазона
  • INVALID_USER_STATUS — пользователь в состоянии, запрещающем действие
  • INVALID_USER_ROLE — у роли нет права на действие
  • PAYMENT_METHOD_NOT_SUPPORTED — комбинация provider/asset не включена для мерчанта
  • AMOUNT_BELOW_MINIMUM — сумма заказа ниже минимального порога (заданного на уровне сети или окружения). Поле details.floor_usd в ответе несёт настроенный порог (в USD), так что вы можете показать его напрямую; текст message тоже его называет.
  • INSUFFICIENT_BALANCE — на кошельке покупателя недостаточно платёжного актива, чтобы покрыть перевод.
  • INSUFFICIENT_GAS — на кошельке покупателя не хватает нативного газа, чтобы отправить перевод.

Платежи / биллинг (402)

  • INSUFFICIENT_CREDIT — предоплаченный баланс мерчанта не покрывает gas / платформенную комиссию. Пополните через панель и повторите

Rate-limit (429)

  • TOO_MANY_REQUESTS — rate-limit по IP на gateway
  • TOO_MANY_ATTEMPTS — повторные неудачные попытки на одном ресурсе (например, OTP) сработали как троттл

Хранилище / инфраструктура (500)

  • DATABASE_CONNECTION_ERROR — не удалось достучаться до БД
  • DATABASE_QUERY_ERROR — план запроса упал на исполнении
  • DATABASE_TRANSACTION_ERROR — commit/rollback не удался
  • REDIS_CONNECTION_ERROR — не удалось достучаться до Redis
  • REDIS_OPERATION_ERROR — команда Redis не удалась
  • EXTERNAL_SERVICE_ERROR — общий сбой стороннего сервиса (provider не выделен отдельно ниже)
  • TWILIO_SERVICE_ERROR — вызов Twilio SMS / Verify не удался
  • SENDGRID_SERVICE_ERROR — отправка письма через SendGrid не удалась
  • INTERNAL_SERVER_ERROR — неожиданный fall-through; снимите заголовок Date ответа и X-RateLimit-* и обратитесь

Доступность (503)

  • SERVICE_UNAVAILABLE — критичный downstream сообщает о нездоровом состоянии; отступ + повтор

Ошибки валидации (400)

Когда проблема в некорректном теле запроса, details — это массив, чтобы вы могли сопоставить ошибки с полями:

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" }, { "field": "success_url", "message": "must be a valid https URL" } ] }

Ошибки аутентификации (401)

INVALID_SIGNATURE покрывает три разных режима отказа — отличить их можно только методом исключения:

  • Неправильный secret-ключ (перепроверьте env var)
  • Расхождение по времени > 5 мин (синхронизируйте NTP)
  • Неправильная каноническая строка (чаще всего: забыли \n-разделители или подписали распарсенное-и-пересериализованное тело, отличающееся от отправленного)

См. Аутентификация для точного алгоритма подписи.

Rate-limit’ы (429)

УровеньЛимит
Каждый маршрут gateway (включая /b2b/v1/*)По IP на gateway — 500 запросов/мин, общая корзина. Не per-merchant.
Публичный checkout (/checkout/*)Более строгая под-корзина — 20 запросов/мин на IP

X-RateLimit-Limit и X-RateLimit-Remaining отправляются на каждом rate-limited ответе gateway (не только на успешных). Считайте их живым бюджетом для вашего IP.

Rate-limited ответы содержат заголовок Retry-After (секунды до сброса окна) — учитывайте его. В качестве fallback отступайте с jitter — 1с базы + экспонента до 30с.

Rate-limit’ы могут меняться. Если вы упираетесь в них легитимно (например, согласуете большой исторический диапазон), напишите нам — bulk-endpoint’ы в roadmap.

Недостаточный кредит (402)

INSUFFICIENT_CREDIT (HTTP 402 Payment Required) означает, что предоплаченный баланс мерчанта не покрывает следующую комиссию gas и платформы для операции, которую вы пытались выполнить — обычно расчёт крипто-платежа или выполнение gas-спонсируемого on-chain действия. Пополните из панели мерчанта (Billing → Add credit) и повторите операцию; работа в полёте подождёт и автоматически продолжится, как только баланс восстановится.

Серверные ошибки (5xx)

500 означает, что мы не смогли обработать запрос. Повторите с отступом — ваш idempotency_key гарантирует, что вы не получите двойное списание, если оригинальный запрос частично выполнился.

Если повторы не восстанавливаются в течение минуты, покажите покупателю обобщённое «Платёж временно недоступен» и обратитесь в поддержку с проблемным endpoint’ом, вашим merchant ID, заголовком Date ответа и значениями X-RateLimit-*, а также приблизительным временем запроса — этого достаточно, чтобы мы нашли соответствующий запрос в наших логах.

Ошибки доставки webhook

Доставки webhook — отдельный канал отказов — они не всплывают как ошибки API, потому что вызывает не ваш сервер. Когда доставка возвращает non-2xx (или истекает по времени), она ставится в очередь повторов с экспоненциальным отступом 0с, 1мин, 5мин, 15мин, 1ч, 6ч (всего шесть попыток — то же расписание, что и на Webhooks → Обзор). Полная история по каждой доставке доступна в Developers → Webhooks → [endpoint] → Delivery log в панели. После шестой попытки доставка переходит в dead-letter — панель показывает бейдж «Failed», который можно вручную проиграть заново, когда ваш сервер восстановится.