# Аутентификация и подпись запросов

> Подписывайте запросы к API с помощью HMAC-SHA256, используя project UUID и API key.

Каждый запрос к API (кроме входящих webhook'ов) должен содержать project UUID и подпись запроса. Подпись подтверждает, что запрос пришёл именно от вас и никто не изменил его по пути.

## API-ключи

В 2328.io используется **два ключа** с одинаковым алгоритмом подписи, но разной областью действия:

| Ключ | Используется для |
|-----|----------|
| **API key** | Платежи, статические кошельки, баланс, курсы валют, а также проверка подписи webhook'ов платежей и статических кошельков |
| **Payout API key** | Все endpoint'ы `/v1/payout/*`, а также проверка подписи webhook'ов выплат |

Оба ключа создаются в настройках проекта на [2328.io](https://2328.io). В примерах ниже фигурирует обобщённое «API key» — подставляйте нужный ключ в зависимости от вызываемого endpoint'а.

> **INFO:** **Никогда** не путайте эти два ключа: подпись запроса на выплату обычным API key (или платежа — payout-ключом) приведёт к ошибке подписи.

## Обязательные заголовки

| Заголовок | Тип | Обязательный | Описание |
|--------|------|----------|-------------|
| `Content-Type` | string | да | Всегда `application/json` |
| `project` | string | да | UUID вашего проекта |
| `sign` | string | да | Подпись запроса HMAC-SHA256, рассчитанная вашим API key |
| `User-Agent` | string | да | Идентифицирует ваше приложение (например, `MyShop/1.4 (+https://myshop.example)`). Запросы без `User-Agent` могут быть заблокированы. |

## Как работает подпись

Подпись — это «отпечаток» тела запроса. Она формируется так:

1. Сериализация тела в JSON (компактный — без лишних пробелов).
2. Кодирование этого JSON в Base64. Этот шаг нормализует данные между языками — после превращения в обычный ASCII любой язык даст одни и те же байты для HMAC.
3. Расчёт **HMAC-SHA256** от строки Base64 с использованием API key, результат в нижнем регистре в hex.

Для **GET** и других запросов без тела подписывайте пустую строку вместо JSON.

> **INFO:** Подпись пустой строки для конкретного API key всегда одинакова. Если у вас много GET-вызовов, её можно закэшировать.

## Реализации

<CodeSnippet name="apiSign" langs="php,js,ts,python,go" />

### Запросы без тела (GET)

Для запросов без тела (например, `GET /v1/payout/status/{uuid}`) подписывайте пустую строку:

<CodeSnippet name="apiSignBodyless" langs="curl,php,js,ts,python,go" />

## Полный пример запроса

<CodeSnippet name="fullRequestExample" langs="curl,php,js,ts,python,go" />

> **DANGER:** **Никогда не раскрывайте API key в клиентском коде.** Все подписи должны вычисляться на вашем бэкенде. Утечка API key даёт постороннему полный доступ к вашему мерчант-аккаунту.

## Проверка подписи webhook'ов

Когда 2328.io отправляет вам webhook, тот же алгоритм применяется в обратную сторону:

1. Извлеките поле `sign` из payload'а.
2. Сериализуйте оставшиеся поля в JSON (компактный, без пробелов).
3. Закодируйте результат в Base64.
4. Рассчитайте `HMAC-SHA256` соответствующим ключом.
5. Сравните результат с полученным `sign` функцией **постоянного времени** (`hash_equals`, `crypto.timingSafeEqual`, `hmac.compare_digest`, `subtle.ConstantTimeCompare`, `OpenSSL.fixed_length_secure_compare`).

Ключ подписи зависит от источника webhook'а:

| Webhook | Ключ для проверки |
|---------|---------------------|
| Webhook'и платежей и статических кошельков (`/v1/payment`, `/v1/static-wallet`) | **API key** |
| Webhook'и выплат (`/v1/payout`) | **Payout API key** |

> **WARNING:** **Типовые ошибки проверки.** Ваш JSON-энкодер должен выдавать **ровно те же байты**, что и отправитель — иначе Base64 будет отличаться и подпись не совпадёт.

- **Go**: используйте `json.NewEncoder` с `SetEscapeHTML(false)`. Стандартный `json.Marshal` экранирует `<`, `>`, `&` как `<` и ломает подпись.
- **Python**: передавайте `ensure_ascii=False` в `json.dumps`. Без этого не-ASCII символы (кириллица, китайский и т. д.) экранируются в `\uXXXX`.
- **Компактный JSON**: без пробелов между полями (`separators=(",", ":")` в Python).
- **Порядок полей** (Go): обычный `map[string]any` рандомизирует ключи при перекодировании. Используйте `json.RawMessage`, упорядоченную структуру либо удаляйте `sign` из исходных байтов.

Если проверка всё равно не проходит, запустите `apiSign` на полезной нагрузке самостоятельно — он должен выдать ту же hex-строку, что и полученный `sign`.

> **INFO:** **Валидная подпись не защищает от повторов.** Она лишь доказывает, что webhook пришёл от 2328.io — но не мешает злоумышленнику позднее переотправить *перехваченный* webhook. Всегда проверяйте идемпотентность по `uuid` (или по `txid` для статических кошельков) перед зачислением средств. Если подпись отсутствует или неверна — отвечайте HTTP `401`.

Полные примеры кода есть на странице **[Webhook-уведомления](/docs/webhooks#verifying-the-signature)**. Обработка повторов и правила идемпотентности — в разделе [Лучшие практики](/docs/webhooks#best-practices).