Аутентификация и подпись
Каждый серверный запрос подписывается вашим секретом API с помощью HMAC-SHA256 — сам секрет никогда не передаётся по сети.
API-ключи
У API-ключа есть публичный id (kx_live_…, передаётся в каждом запросе) и секрет (kxs_…, используется только для подписи). Сотрудники с правом «Управление API и вебхуками» создают ключи в бэк-офисе, выбирают права, разрешённые IP, лимит запросов и срок действия, а также в любой момент могут сменить секрет или отозвать ключ.
Секрет показывается один раз — при создании или смене ключа. Храните его в хранилище секретов; сотрудники KinetX не смогут показать его снова.
Подпись запросов
Отправляйте три заголовка с каждым запросом:
| X-Api-Key | Ваш публичный id ключа. |
| X-Timestamp | Текущее Unix-время в секундах. Запросы, отличающиеся от часов сервера более чем на 300 секунд, отклоняются. |
| X-Signature | Hex HMAC-SHA256 строки для подписи в нижнем регистре, ключ — ваш секрет. |
| X-Nonce | Необязательно. Уникальное значение, добавляемое к строке для подписи, — нужно, только если вы можете отправить побайтно одинаковые запросы в одну и ту же секунду. |
Строка для подписи объединяет четыре поля через перевод строки (\n): HTTP-метод в верхнем регистре, URI запроса точно в отправленном виде (путь и строка запроса, начиная с /api/v1), метку времени и сырое тело (пустая строка для GET). При X-Nonce значение добавляется пятой строкой.
POST
/api/v1/operator/players?source=web
1760520600
{"username":"jane_doe",…}
Каждая подпись принимается один раз: перехваченный запрос нельзя повторить (REPLAYED_REQUEST). Подписывайте ровно те байты, которые отправляете, — сериализуйте JSON один раз и используйте одну и ту же строку для подписи и тела.
Примеры
Регистрация игрока подписанным идемпотентным POST:
API_KEY="kx_live_your_key_id"
API_SECRET="kxs_your_secret"
URI="/api/v1/operator/players"
BODY='{"username":"jane_doe","email":"jane@example.com","first_name":"Jane","last_name":"Doe","birth_date":"1990-04-12","country":"MT","terms_accepted":true}'
TS=$(date +%s)
# METHOD \n REQUEST_URI \n TIMESTAMP \n BODY → hex HMAC-SHA256 with your secret
SIG=$(printf '%s\n%s\n%s\n%s' "POST" "$URI" "$TS" "$BODY" \
| openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed 's/^.* //')
curl -sS -X POST "https://kinetx.amhtechnologies.com$URI" \
-H "Content-Type: application/json" \
-H "X-Api-Key: $API_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-H "Idempotency-Key: $(uuidgen)" \
--data "$BODY"
<?php
function kinetx(string $method, string $uri, ?array $payload = null, ?string $idempotencyKey = null): array
{
$apiKey = getenv('KINETX_API_KEY'); // kx_live_…
$secret = getenv('KINETX_API_SECRET'); // kxs_…
$body = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$timestamp = (string) time();
$signature = hash_hmac('sha256', strtoupper($method)."\n{$uri}\n{$timestamp}\n{$body}", $secret);
$headers = [
'Accept: application/json',
'Content-Type: application/json',
"X-Api-Key: {$apiKey}",
"X-Timestamp: {$timestamp}",
"X-Signature: {$signature}",
];
if ($idempotencyKey !== null) {
$headers[] = "Idempotency-Key: {$idempotencyKey}";
}
$curl = curl_init('https://kinetx.amhtechnologies.com'.$uri);
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_POSTFIELDS => $body === '' ? null : $body,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
$response = json_decode((string) curl_exec($curl), true);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}", $status);
}
return $response;
}
$player = kinetx('POST', '/api/v1/operator/players', [
'username' => 'jane_doe',
'email' => 'jane@example.com',
'first_name' => 'Jane',
'last_name' => 'Doe',
'birth_date' => '1990-04-12',
'country' => 'MT',
'terms_accepted' => true,
], idempotencyKey: bin2hex(random_bytes(16)))['data'];
$wallet = kinetx('GET', "/api/v1/operator/players/{$player['id']}/wallet")['data'];
import crypto from 'node:crypto';
const HOST = 'https://kinetx.amhtechnologies.com';
export async function kinetx(method, uri, payload, idempotencyKey) {
const body = payload === undefined ? '' : JSON.stringify(payload);
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = crypto
.createHmac('sha256', process.env.KINETX_API_SECRET)
.update(`${method.toUpperCase()}\n${uri}\n${timestamp}\n${body}`)
.digest('hex');
const response = await fetch(HOST + uri, {
method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Api-Key': process.env.KINETX_API_KEY,
'X-Timestamp': timestamp,
'X-Signature': signature,
...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
},
body: body === '' ? undefined : body,
});
const json = await response.json();
if (!response.ok) throw Object.assign(new Error(json.error.message), json.error);
return json;
}
const { data: player } = await kinetx('POST', '/api/v1/operator/players', {
username: 'jane_doe',
email: 'jane@example.com',
first_name: 'Jane',
last_name: 'Doe',
birth_date: '1990-04-12',
country: 'MT',
terms_accepted: true,
}, crypto.randomUUID());
Права доступа
Ключ может вызывать только методы, права на которые у него есть; иначе API отвечает 403 INSUFFICIENT_SCOPE. Давайте каждой интеграции минимально необходимый набор.
| players:read | Чтение игроков, их профилей и лимитов ответственной игры |
| players:write | Регистрация игроков, изменение профилей и статуса, выдача токенов сессии игрока |
| limits:write | Установка лимитов ответственной игры и самоисключений |
| wallet:read | Чтение балансов кошелька и журнала операций |
| payments:read | Чтение депозитов, выводов и платёжных методов |
| payments:write | Запись оплаченных депозитов и создание заявок на вывод |
| games:read | Чтение каталога игр |
| games:launch | Запуск игр для игроков (реальный и демо-режим) |
| bonuses:read | Чтение каталога бонусов и бонусов игроков |
| bonuses:write | Начисление бонусов и активация промокодов |
| reports:read | Чтение сводок GGR и финансов |
Список разрешённых IP
Ограничьте ключ адресами своих серверов: отдельные IPv4/IPv6-адреса или CIDR-диапазоны, например 203.0.113.0/24. Вызовы с других адресов получают 403 IP_NOT_ALLOWED. Пустой список разрешает любой адрес.
Токены сессии игрока
Ваш фронтенд никогда не должен хранить секрет API. Когда игрок входит на ваш сайт, ваш сервер выдаёт короткоживущий токен сессии игрока (POST /api/v1/operator/players/{player}/session-tokens, по умолчанию 30 минут, максимум 720) и передаёт его браузеру или приложению, которое вызывает методы игрока с Authorization: Bearer kxp_….
// Your server: POST /api/v1/operator/players/1042/session-tokens → { data: { token, expires_at } }
// Your front end (never holds the API secret):
const res = await fetch('https://kinetx.amhtechnologies.com/api/v1/player/games/311/launch', {
method: 'POST',
headers: { Authorization: `Bearer ${playerToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: 'real', platform: 'mobile' }),
});
const { data } = await res.json();
window.location.href = data.launch_url;
Токен даёт доступ только к своему игроку (профиль, балансы, транзакции, запуск игр, KinetX Originals). Он перестаёт работать по истечении срока или при отзыве выдавшего его API-ключа.
Чек-лист безопасности
- Храните секреты на сервере, в хранилище секретов — никогда во фронтенд-коде, мобильных приложениях или репозиториях.
- Используйте отдельный ключ для каждой интеграции и окружения, с минимумом прав и списком разрешённых IP.
- Регулярно меняйте секреты и немедленно — при подозрении на утечку; отзывайте неиспользуемые ключи.
- Синхронизируйте часы сервера (NTP): от них зависят подписи.
- Проверяйте подпись каждого вебхука, прежде чем доверять ему.