# Автентифікація та підписання запитів

> Підписуйте API-запити за допомогою HMAC-SHA256, використовуючи project UUID та API key.

Кожен API-запит (окрім вхідних webhooks) повинен містити ваш project UUID та підпис запиту. Підпис підтверджує, що запит надійшов саме від вас і ніхто його не змінив під час передачі.

## API-ключі

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

| Ключ | Використовується для |
|------|----------------------|
| **API key** | Платежі, статичні гаманці, баланс, курси обміну, а також перевірка webhooks для платежів та статичних гаманців |
| **Payout API key** | Усі endpoints `/v1/payout/*` та перевірка webhooks для виплат |

Обидва ключі знаходяться у налаштуваннях вашого проєкту на [2328.io](https://2328.io). У наведених нижче прикладах "API key" — узагальнена назва; підставте відповідний ключ для endpoint, який викликаєте.

> **INFO:** **Ніколи** не змішуйте два ключі: підписання запиту виплати звичайним API key (або платіжного запиту ключем виплат) повертає помилку підпису.

## Обов'язкові заголовки

| Заголовок | Тип | Обов'язковий | Опис |
|-----------|-----|--------------|------|
| `Content-Type` | string | так | Завжди `application/json` |
| `project` | string | так | Ваш project 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 надає будь-кому повний доступ до вашого мерчант-акаунту.

## Перевірка підписів webhooks

Коли 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 | Ключ для перевірки |
|---------|---------------------|
| Webhooks платежів та статичних гаманців (`/v1/payment`, `/v1/static-wallet`) | **API key** |
| Webhooks виплат (`/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` на payload самостійно — він має видавати той самий hex-рядок, що й отриманий `sign`.

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

Повні приклади коду на сторінці **[Webhook-сповіщення](/docs/webhooks#verifying-the-signature)**. Обробка повторних спроб і правила ідемпотентності — у розділі [Найкращі практики](/docs/webhooks#best-practices).