GH GambleHub

Обработка ошибок и коды статусов

1) Зачем стандартизировать ошибки

Единый контракт ошибок ускоряет отладку клиентов, снижает ложные ретраи и делает RCA воспроизводимым. Хорошая система:
  • предсказуемо кодирует тип проблемы,
  • дает клиенту действуемые подсказки (что делать дальше),
  • защищает от утечки внутренних деталей,
  • совместима с ретраями и идемпотентностью.

2) Принципы дизайна

1. Одна схема ошибок для всех сервисов (REST/GraphQL/gRPC/webhooks).
2. Четкая семантика ретраев: какие коды ретраить, какие — нет.
3. Fail-closed на write-операциях: лучше 4xx/5xx, чем тихая неконсистентность.
4. Без утечек: не раскрывать SQL, стеки, конфиги, внутренние ID.
5. Трассировка: всегда возвращайте `trace_id`/`correlation_id`.
6. Локализация сообщений — опционально, но коды и `reason` остаются стабильными.


3) Единый формат (Problem Details / JSON)

Рекомендуемый базовый формат (совместим с RFC 7807):
json
{
"type": "https://errors.example.com/auth/invalid-token",
"title": "Invalid access token",
"status": 401,
"code": "AUTH_INVALID_TOKEN",
"detail": "Token expired or signature invalid.",
"instance": "/api/v1/payments/12345",
"trace_id": "01HX3...ABC",
"hint": "Obtain a new token via OAuth2 refresh.",
"meta": {
"scope": "payments:write",
"policy": "deny-by-default"
}
}
Пояснения:
  • `type` — стабильный URL-идентификатор класса ошибок.
  • `code` — короткий машинный код домена (стабилен между релизами).
  • `hint` — что делать клиенту (повтор, обновить токен, изменить параметры).
  • `meta` — безопасные детали (без секретов и PII).

4) Карта кодов статусов (минимальный набор)

Аутентификация/авторизация

400 Bad Request — структурная валидация/схема.
401 Unauthorized — нет/невалиден токен. Добавляйте `WWW-Authenticate`.
403 Forbidden — аутентифицирован, но нет прав/политики отказали.
404 Not Found — маскируйте существование ресурса при отсутствии прав.
409 Conflict — конфликт версий/состояний (optimistic lock, идемпотентность).
451 Unavailable For Legal Reasons — блок по комплаенсу/юрисдикции.

Лимиты и защита

408 Request Timeout — клиент слишком медленно отправляет тело.
409/ 425 Too Early — запрет раннего повтора в 0-RTT/TLS 1.3.
429 Too Many Requests — с `Retry-After` и политикой лимита.
499 Client Closed Request — (на периметре/NGINX) клиент разорвал соединение.

Данные и бизнес-правила

422 Unprocessable Content — бизнес-валидация прошла схему, но смысл неверен.
423 Locked — ресурс заблокирован (KYC review, AML freeze).
409 Conflict — двойная отправка, гонка, лимит состояния (например, «уже в обработке»).
410 Gone — эндпоинт/ресурс удален (депрекейт завершен).

Серверные

500 Internal Server Error — неизвестная ошибка; не раскрывать детали.
502 Bad Gateway — зависимость вернула ошибку/проксирование.
503 Service Unavailable — деградация/плановые работы; добавить `Retry-After`.
504 Gateway Timeout — таймаут зависимостей.

💡 Правило: write-операции при сомнении → 409 (конфликт) или 503 (повтор позже), не 200.

5) Семантика ретраев и идемпотентности

Нельзя ретраить: 400/401/403/404/422 (если клиент не изменил запрос).
Можно ретраить: 408/429/5xx/425/499/504 (с backoff + jitter).
Идемпотентность: для `POST` включайте `Idempotency-Key` (UUIDv4).

На конфликт повторного выполнения возвращайте 409 с `hint: "Use same Idempotency-Key or GET status"`.
Добавляйте `Idempotency-Replay: true` при возврате сохраненного результата.

Пример headers при 429:

HTTP/1.1 429 Too Many Requests
Retry-After: 3
RateLimit-Limit: 50
RateLimit-Remaining: 0
RateLimit-Reset: 1730641030

6) Валидация входа: структура ошибок полей

Для 400/422 используйте массив ошибок полей:
json
{
"type": "https://errors.example.com/validation",
"title": "Validation failed",
"status": 422,
"code": "VALIDATION_ERROR",
"trace_id": "01HX4...XYZ",
"errors": [
{"field": "amount", "rule": "min", "message": "Must be >= 10"},
{"field": "currency", "rule": "enum", "message": "Unsupported currency"}
]
}

7) Частичные неудачи (batch/partial failure)

В батч-эндпоинтах не прячьте ошибки внутри 200 без структуры. Возвращайте 207 Multi-Status или 200 c массивом результатов, где каждое задание имеет свой статус:
json
{
"status": "partial",
"succeeded": 8,
"failed": 2,
"results": [
{"id": "op1", "status": 201},
{"id": "op2", "status": 422, "error": {"code":"VALIDATION_ERROR","detail":"..."}}
]
}

8) Пагинация и «пустые» ответы

Пустая коллекция — 200 с `items: []`, не 404.
Конец страницы — `next_page_token` отсутствует.
Некорректный токен — 400 с `code: PAGINATION_CURSOR_INVALID`.


9) Webhooks: надежная доставка

Подписывайте события (HMAC) и проверяйте до обработки.
Ответ на успешную обработку — 2xx (лучше 204).
Временные сбои получателя — 5xx; отправитель повторяет (экспоненциальный backoff, джиттер).
Дедупликация по `event_id` и сохранение результата (idempotent consumer).
Недопустимый payload — 400/422 без повторов.


10) Соответствия протоколам (gRPC/GraphQL)

gRPC → HTTP:
  • `INVALID_ARGUMENT` → 400
  • `UNAUTHENTICATED` → 401
  • `PERMISSION_DENIED` → 403
  • `NOT_FOUND` → 404
  • `ALREADY_EXISTS` → 409
  • `FAILED_PRECONDITION` → 412/422
  • `RESOURCE_EXHAUSTED` → 429
  • `ABORTED` → 409
  • `UNAVAILABLE` → 503
  • `DEADLINE_EXCEEDED` → 504
GraphQL: всегда 200 на транспортном уровне допустим, но помещайте ошибки в `errors[]` и дублируйте в заголовках/расширениях:
json
{
"data": { "createPayment": null },
"errors": [{
"message": "Forbidden",
"extensions": { "code": "FORBIDDEN", "status": 403, "trace_id": "..." },
"path": ["createPayment"]
}]
}

Рекомендуется для критичных ошибок использовать не 200, а соответствующий HTTP-код.


11) Заголовки и подсказки клиенту

`Retry-After` — секунды/HTTP-дата (429/503/425/408).
`Warning` — мягкие деградации или депрекейт («199 - Feature X is deprecated»).
`Deprecation`, `Sunset`, `Link: <...>; rel="deprecation"` — для управляемого отключения.
`Problem-Type` (кастомный) — быстрый роутинг ошибок на клиенте.
`X-Trace-Id`/`Correlation-Id` — связывает логи/трейсы.


12) Безопасность сообщений

Не повторяйте входные секреты (токены/подписи) в теле ответа.
Маскируйте PAN/PII (`1234`).
Для 401/403 — не раскрывайте, какой именно атрибут провалился.
Для 404 вместо «resource exists but not yours» — просто 404.


13) Наблюдаемость ошибок

Метрики:
  • `http_errors_total{status, route, tenant}`
  • `error_classes_total{code}` (по `code` из тела)
  • доля 429, 5xx; `p95`/`p99` latency для ошибочных ответов отдельно
  • `retry_after_seconds_bucket` — гистограмма советов по повторам
Логи/трейсы:
  • связывайте ответ с `trace_id`, храните `code`, `type`, `status`, `route`, `tenant`, без PII.
Алерты:
  • всплеск `5xx_rate > X%` при RPS>N;
  • рост 429 на критичных маршрутах;
  • `timeout/504` у зависимостей;
  • частые 409/идемпотентность → признак гонок.

14) Примеры

14.1 422 (бизнес-валидация)

json
{
"type": "https://errors.example.com/payments/limit-exceeded",
"title": "Limit exceeded",
"status": 422,
"code": "PAYMENT_LIMIT_EXCEEDED",
"detail": "Daily withdrawal limit reached for KYC1.",
"hint": "Increase limits after KYC2 or try tomorrow.",
"trace_id": "01J5...XYZ"
}

14.2 409 (идемпотентность)


HTTP/1.1 409 Conflict
Idempotency-Replay: true
json
{
"type": "https://errors.example.com/idempotency/replay",
"title": "Duplicate request",
"status": 409,
"code": "IDEMPOTENT_REPLAY",
"detail": "A request with the same Idempotency-Key was already processed.",
"hint": "Reuse the same Idempotency-Key and GET the operation status."
}

14.3 429 (лимиты)

json
{
"type":"https://errors.example.com/rate/too-many-requests",
"title":"Too many requests",
"status":429,
"code":"RATE_LIMITED",
"detail":"Per-key rate limit exceeded.",
"hint":"Retry after the time specified in Retry-After header."
}

15) Антипаттерны

Возвращать 200 с текстом ошибки в теле.
Смешивать разные форматы ошибок между сервисами.
Раскрывать стек/SQL/имена таблиц/внутренние URL в `detail`.
Использовать `message` вместо стабильного `code`/`type`.
Возвращать 500 при ожидаемой бизнес-ошибке (например, «баланс недостаточен»).
Непоследовательная семантика между REST/GraphQL/gRPC.


16) Специфика iGaming/финансов

Четкие коды для KYC/AML/санкций: `KYC_REQUIRED`, `KYC_REVIEW`, `AML_LOCK`, `SANCTION_BLOCKED`.
Ограничения юрисдикций: 451 с безопасной формулировкой без указания списков.
Денежные write-операции: 409/423 при конкуренции и блокировках, `hint` с окном повтора.
Инварианты лимитов игрока: используйте 422 для нарушений правил ответственных платежей.
Аудит: неизменяемые журналы решений (код, время, актор, trace_id).


17) Чек-лист prod-готовности

  • Единая JSON-схема ошибок, стабильные `type`/`code`.
  • Маппинг HTTP ↔ gRPC/GraphQL согласован и задокументирован.
  • Семантика ретраев + `Retry-After`; идемпотентность для write.
  • Маскирование PII/секретов; 404 для скрытия ресурсов.
  • Метрики ошибок и алерты; корелляция с `trace_id`.
  • Политики депрекейта: `Deprecation`, `Sunset`, `Link`.
  • Тесты: negative/fuzz, конфликт версий, падение зависимостей, double-submit.
  • Гайд клиентам: примеры бэк-оффов и обработка 409/422/429/5xx.

18) TL;DR

Стандартизируйте единый JSON-формат ошибок c `type`/`code`/`trace_id`, используйте корректные HTTP-коды, различайте валидацию (400/422), права (401/403/404), конфликты/идемпотентность (409) и лимиты (429). Давайте четкие `Retry-After` и `hint`, маскируйте чувствительные данные, логируйте ошибки с `trace_id` и стройте алерты по 5xx/429/p99.

Contact

Свяжитесь с нами

Обращайтесь по любым вопросам или за поддержкой.Мы всегда готовы помочь!

Начать интеграцию

Email — обязателен. Telegram или WhatsApp — по желанию.

Ваше имя необязательно
Email необязательно
Тема необязательно
Сообщение необязательно
Telegram необязательно
@
Если укажете Telegram — мы ответим и там, в дополнение к Email.
WhatsApp необязательно
Формат: +код страны и номер (например, +380XXXXXXXXX).

Нажимая кнопку, вы соглашаетесь на обработку данных.