---
title: Підпис запитів CP-HMAC
description: Як підписувати запити до API Coinetic: заголовок Authorization CP-HMAC, рядок підпису, nonce, допустиме розходження годинників та еталонні значення.
url: https://coinetic.money/uk/docs/authentication/
lang: uk
---

# Підпис запитів CP-HMAC

Кожен запит до `/v1/*` підписується секретом API-ключа. Сам секрет мережею не передається. Готові клієнти роблять це за вас — цей розділ для тих, хто пише власний.

## Заголовок

```http
Authorization: CP-HMAC key=<key_id>, ts=<unix_seconds>, nonce=<nonce>, sig=<hex>
```

| Поле | Формат |
| --- | --- |
| `key` | id ключа: `ak_` + 16–40 символів `[a-z0-9]` |
| `ts` | Поточний час, секунди Unix. Допустиме розходження з годинником сервера — **±5 хвилин** |
| `nonce` | Одноразовий рядок `[A-Za-z0-9_-]{16,64}`; повтор того самого nonce відхиляється |
| `sig` | 64 hex-символи в нижньому регістрі |

## Що підписується

```
string = METHOD + "\n" + PATH_WITH_QUERY + "\n" + ts + "\n" + nonce + "\n" + hex(sha256(body))
sig    = hex(HMAC-SHA256(secret, string))
```

- `METHOD` — `GET`, `POST` тощо.
- `PATH_WITH_QUERY` — шлях і query-рядок точно так, як вони підуть у запиті: `/v1/invoices?status=paid&limit=50`. Схема й хост не підписуються.
- Тіло підписується байт у байт. Підпишіть той самий рядок JSON, який надішлете, а не повторно серіалізований об’єкт.
- Для запитів без тіла `hex(sha256(""))` = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`.

**Node.js** — `sign.js`

```js
import { createHash, createHmac, randomBytes } from "node:crypto";

export function sign(keyId, secret, method, pathWithQuery, body = "") {
  const ts = Math.floor(Date.now() / 1000).toString();
  const nonce = randomBytes(16).toString("hex");
  const bodyHash = createHash("sha256").update(body).digest("hex");
  const sig = createHmac("sha256", secret)
    .update(`${method}\n${pathWithQuery}\n${ts}\n${nonce}\n${bodyHash}`)
    .digest("hex");
  return `CP-HMAC key=${keyId}, ts=${ts}, nonce=${nonce}, sig=${sig}`;
}
```

**PHP** — `sign.php`

```php
function cp_sign(string $keyId, string $secret, string $method, string $pathWithQuery, string $body = ''): string {
    $ts = (string) time();
    $nonce = bin2hex(random_bytes(16));
    $str = $method . "\n" . $pathWithQuery . "\n" . $ts . "\n" . $nonce . "\n" . hash('sha256', $body);
    $sig = hash_hmac('sha256', $str, $secret);
    return "CP-HMAC key=$keyId, ts=$ts, nonce=$nonce, sig=$sig";
}
```

**Python** — `sign.py`

```py
import hashlib, hmac, secrets, time

def cp_sign(key_id: str, secret: str, method: str, path_with_query: str, body: bytes = b"") -> str:
    ts = str(int(time.time()))
    nonce = secrets.token_hex(16)
    msg = f"{method}\n{path_with_query}\n{ts}\n{nonce}\n{hashlib.sha256(body).hexdigest()}"
    sig = hmac.new(secret.encode(), msg.encode(), hashlib.sha256).hexdigest()
    return f"CP-HMAC key={key_id}, ts={ts}, nonce={nonce}, sig={sig}"
```

**Go** — `sign.go`

```go
sig := cryptopayclient.SignRequest(secret, "POST", "/v1/invoices", ts, nonce, body)
req.Header.Set("Authorization", fmt.Sprintf("CP-HMAC key=%s, ts=%d, nonce=%s, sig=%s", keyID, ts, nonce, sig))
```

## Еталонні значення

Звірте свою реалізацію — на цих самих значеннях перевіряються всі чотири офіційні клієнти.

| Секрет | `test-secret-0123456789abcdef0123456789` |
| --- | --- |
| Запит 1 | `POST /v1/invoices`, ts `1760000000`, nonce `nonce-0000000001`, тіло `{"order_id":"WISH-1"}` |
| sig 1 | `2c769f6a5be7b80871e03851ccfe84002130ec48a45348bd821752a6d45960ec` |
| Запит 2 | `GET /v1/invoices/inv_abcdefabcdefabcdef`, ts `1760000000`, nonce `nonce-0000000002`, без тіла |
| sig 2 | `a12924acf8a772c6f3fadb8874ff69e9934046ccd2fa5563b23fec3e98e1665b` |

## Якщо отримуєте 401

Будь-яка помилка підпису — `401 unauthorized` з однаковим текстом: сервер не підказує, що саме не так. Поширені причини:

- годинник сервера відхилився більш ніж на 5 хвилин — увімкніть NTP;
- підписано інший шлях — без query або з іншим порядком параметрів;
- тіло повторно серіалізовано після підпису;
- nonce використано повторно;
- ключ відкликано.

`403 merchant_suspended` — підпис правильний, але мерчанта призупинено.

## Ключі

- Ключі створюються в кабінеті, розділ «Ключі та вебхуки». Секрет показується **один раз**.
- Одночасно діє до 10 ключів. Ротація: створіть новий, перемкніть сервер, відкличте старий. Відкликання діє одразу.
- Зберігайте секрет у менеджері секретів або змінній оточення сервера, не в коді й не в git.
- Ліміт — близько 20 запитів на секунду на мерчанта із запасом на сплески; у разі перевищення — `429 rate_limited`.
