Перейти к содержанию

Аутентификация

Каждый запрос аутентифицируется подписанным API-ключом (HMAC-SHA256). Администратор вашей организации создаёт ключ в админ-панели и передаёт его вашему бэкенду, который подписывает каждый запрос.


Обзор

Каждый запрос должен содержать три заголовка, вычисленных из вашего Key ID и Secret:

Заголовок Описание
X-API-Key Идентификатор ключа (публичный)
X-Timestamp Текущее Unix-время в секундах
X-Signature HMAC-SHA256 от канонической строки запроса

Получение учётных данных

Администратор организации создаёт ключ в админ-панели (Settings → API Keys → Create API Key) и передаёт вам Key ID и Secret. Секрет показывается только один раз при создании.

Поля в ответе:

Поле Описание
keyId Публичный идентификатор — используйте как заголовок X-API-Key
secret Секрет для подписи HMAC — никогда не публикуйте

Храните секрет надёжно

Секрет показывается только один раз при создании. Сохраните его в менеджере секретов, переменной окружения или защищённом хранилище. Позже его восстановить нельзя.


Подпись запроса

Шаг 1: Каноническая строка

Каноническая строка состоит из четырёх компонентов, разделённых переводом строки:

{timestamp}\n{METHOD}\n{path}\n{bodyHash}
Компонент Описание Пример
timestamp Unix-время в секундах (то же, что в X-Timestamp) 1708600000
METHOD HTTP-метод заглавными буквами POST
path Путь запроса с ведущим слешем, без хоста /vaults
bodyHash SHA-256 от тела запроса в hex (для пустого тела — хеш пустой строки) e3b0c44298fc1c14...

Шаг 2: HMAC-подпись

Подпишите каноническую строку секретом по алгоритму HMAC-SHA256, затем закодируйте результат в hex-строку нижнего регистра.

Шаг 3: Отправка запроса

Передайте все три заголовка:

X-API-Key: {keyId}
X-Timestamp: {timestamp}
X-Signature: {hexSignature}

Окно по времени

Сервер отклоняет запросы со временем старше 30 секунд. Убедитесь, что часы вашего сервера синхронизированы (NTP).


Примеры кода

Python

import hashlib
import hmac
import time
import requests

API_KEY_ID = "your-key-id"
API_SECRET = "your-secret"
BASE_URL = "https://api.example.com"

def sign_request(method: str, path: str, body: str = "") -> dict:
    timestamp = str(int(time.time()))
    body_hash = hashlib.sha256(body.encode()).hexdigest()
    canonical = f"{timestamp}\n{method}\n{path}\n{body_hash}"
    signature = hmac.new(
        API_SECRET.encode(), canonical.encode(), hashlib.sha256
    ).hexdigest()
    return {
        "X-API-Key": API_KEY_ID,
        "X-Timestamp": timestamp,
        "X-Signature": signature,
    }

# GET-запрос
path = "/vaults"
headers = sign_request("GET", path)
response = requests.get(f"{BASE_URL.rstrip('/')}{path}", headers=headers)

# POST-запрос
path = "/vaults"
body = '{"externalId":"cust_123","name":"Alice"}'
headers = sign_request("POST", path, body)
headers["Content-Type"] = "application/json"
response = requests.post(f"{BASE_URL.rstrip('/')}{path}", headers=headers, data=body)

Node.js

const crypto = require("crypto");

const API_KEY_ID = "your-key-id";
const API_SECRET = "your-secret";

function signRequest(method, path, body = "") {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
  const canonical = `${timestamp}\n${method}\n${path}\n${bodyHash}`;
  const signature = crypto
    .createHmac("sha256", API_SECRET)
    .update(canonical)
    .digest("hex");
  return {
    "X-API-Key": API_KEY_ID,
    "X-Timestamp": timestamp,
    "X-Signature": signature,
  };
}

// GET
const headers = signRequest("GET", "/vaults");
fetch("https://api.example.com/vaults", { headers });

// POST
const body = JSON.stringify({ externalId: "cust_123", name: "Alice" });
const postHeaders = signRequest("POST", "/vaults", body);
postHeaders["Content-Type"] = "application/json";
fetch("https://api.example.com/vaults", {
  method: "POST",
  headers: postHeaders,
  body,
});

C

using System.Security.Cryptography;
using System.Text;

var apiKeyId = "your-key-id";
var apiSecret = "your-secret";

string SignRequest(string method, string path, string body = "")
{
    var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
    var bodyHash = Convert.ToHexStringLower(
        SHA256.HashData(Encoding.UTF8.GetBytes(body)));
    var canonical = $"{timestamp}\n{method}\n{path}\n{bodyHash}";
    var signature = Convert.ToHexStringLower(
        HMACSHA256.HashData(
            Encoding.UTF8.GetBytes(apiSecret),
            Encoding.UTF8.GetBytes(canonical)));
    return signature; // Заголовки X-API-Key, X-Timestamp, X-Signature заполните самостоятельно
}

cURL (Bash)

API_KEY_ID="your-key-id"
API_SECRET="your-secret"
TIMESTAMP=$(date +%s)
METHOD="GET"
PATH_URL="/vaults"
BODY=""

BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')
CANONICAL="${TIMESTAMP}\n${METHOD}\n${PATH_URL}\n${BODY_HASH}"
SIGNATURE=$(echo -ne "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | awk '{print $NF}')

curl "https://api.example.com${PATH_URL}" \
  -H "X-API-Key: $API_KEY_ID" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"

Список разрешённых IP

API-ключ можно ограничить определёнными IP-адресами. При настроенном списке запросы с других адресов получают 401 Unauthorized.

Список настраивается в админ-панели: Settings → API Keys → Edit.


Ответы об ошибках

Статус Что значит
401 Unauthorized Ключ отсутствует, недействителен или просрочен. Подпись не совпадает. IP не разрешён. Расхождение по времени > 30 с. Повтор — та же тройка (keyId, timestamp, signature) уже принята в окне сдвига; пересчитайте подпись с новым timestamp.
403 Forbidden Учётные данные верны, но скоупа недостаточно для запрошенной операции.
429 Too Many Requests Превышен лимит (по умолчанию 120 запросов/мин/ключ). Уважайте Retry-After.

За пределами аутентификации

Три рантайм-поведения входят в контракт партнёра — прочитайте до релиза:

  • Idempotency-Key обязателен на POST /transactions/withdraw и POST /transactions/transfer. Генерируйте UUID на каждую логическую операцию; повторы возвращают исходный ответ.
  • Защита от повторов. Каждая принятая подпись одноразова в пределах 30-секундного окна — повтор ОБЯЗАН пересчитать подпись.
  • Rate limit. 120 авторизованных запросов / мин / ключ, sliding window. В 429 есть Retry-After.

Полный справочник: Надёжность и лимиты.


Дальше