Вебхуки
Получайте уведомления в момент любого события на платформе — регистрации, платежи, бонусы, решения KYC и сигналы комплаенса — подписанными HTTPS-запросами на ваши серверы.
Обзор
Сотрудники добавляют адреса вебхуков в бэк-офисе (Инструменты → API и вебхуки → Вебхуки): HTTPS-адрес, события, на которые он подписан, и секрет подписи, показываемый один раз. Кнопка «Отправить тестовое событие» отправляет ping, а журнал доставок показывает каждую попытку с ответом и позволяет повторить её.
События возникают для всего, что происходит в вашем аккаунте оператора, — через API, ваш сайт на KinetX, бэк-офис или плановую задачу.
Каждая доставка — POST с телом JSON и следующими заголовками:
| X-KinetX-Event | Тип события, например deposit.paid. |
| X-KinetX-Event-Id | Уникальный id события (есть и в теле); используйте его, чтобы обработать каждое событие один раз. |
| X-KinetX-Delivery | Id доставки, одинаковый для всех повторов этой доставки. |
| X-KinetX-Timestamp | Unix-время этой попытки, часть подписи. |
| X-KinetX-Signature | Hex HMAC-SHA256 от «{timestamp}.{сырое тело}» с ключом — секретом адреса. |
События
Тело всегда имеет одну обёртку — id, type, api_version, created_at и data события. Объекты data — те же ресурсы, что возвращает API.
player.registered
Игрок зарегистрировался (сайт, бэк-офис или API)
{
"id": "evt_01jaq3w8z6x4t2v9r7p5n3m1k0",
"type": "player.registered",
"api_version": "v1",
"created_at": "2026-10-15T09:30:00Z",
"data": {
"player": {
"id": 1042,
"external_id": "op-58213",
"username": "jane_doe",
"email": "jane@example.com",
"first_name": "Jane",
"middle_name": null,
"last_name": "Doe",
"gender": "female",
"birth_date": "1990-04-12",
"country": "MT",
"city": "Valletta",
"address": "12 Republic Street",
"zip_code": "VLT 1110",
"phone": "+35699123456",
"personal_id": null,
"document_number": "P1234567",
"language": "en",
"currency": "EUR",
"status": "active",
"category": "new_user",
"kyc_status": "not_started",
"is_verified": false,
"risk_level": "low",
"is_test": false,
"restrictions": {
"can_login": true,
"can_deposit": true,
"can_withdraw": true,
"can_bet": true,
"excluded_from_bonuses": false
},
"self_exclusion": null,
"marketing": {
"email": true,
"sms": false,
"newsletter": false
},
"registration": {
"source": "api",
"ip": "203.0.113.10",
"promo_code": "WELCOME100",
"affiliate_id": "aff-204",
"btag": null
},
"last_login_at": null,
"created_at": "2026-10-15T09:30:00Z",
"updated_at": "2026-10-15T09:30:00Z"
}
}
}
deposit.paid
Депозит оплачен и зачислен на кошелёк
{
"id": "evt_01jaq3w8z6x4t2v9r7p5n3m1k0",
"type": "deposit.paid",
"api_version": "v1",
"created_at": "2026-10-15T09:30:00Z",
"data": {
"payment": {
"id": 5120,
"type": "deposit",
"status": "paid",
"player_id": 1042,
"amount": 10000,
"currency": "EUR",
"payment_method": "visa-mastercard",
"external_reference": "psp_tx_7781",
"info": null,
"reject_reason": null,
"is_reversed": false,
"created_at": "2026-10-15T09:30:00Z",
"paid_at": "2026-10-15T09:30:00Z",
"rejected_at": null,
"cancelled_at": null,
"updated_at": "2026-10-15T09:30:00Z"
},
"player": {
"id": 1042,
"external_id": "op-58213",
"username": "jane_doe"
}
}
}
withdrawal.requested
Игрок запросил вывод (сумма зарезервирована)
{
"id": "evt_01jaq3w8z6x4t2v9r7p5n3m1k0",
"type": "withdrawal.requested",
"api_version": "v1",
"created_at": "2026-10-15T09:30:00Z",
"data": {
"payment": {
"id": 5121,
"type": "withdrawal",
"status": "new",
"player_id": 1042,
"amount": 5000,
"currency": "EUR",
"payment_method": "visa-mastercard",
"external_reference": "wd_20261015_001",
"info": "Visa **** 4242",
"reject_reason": null,
"is_reversed": false,
"created_at": "2026-10-15T09:30:00Z",
"paid_at": null,
"rejected_at": null,
"cancelled_at": null,
"updated_at": "2026-10-15T09:30:00Z"
},
"player": {
"id": 1042,
"external_id": "op-58213",
"username": "jane_doe"
}
}
}
withdrawal.paid
Вывод выплачен
{
"id": "evt_01jaq3w8z6x4t2v9r7p5n3m1k0",
"type": "withdrawal.paid",
"api_version": "v1",
"created_at": "2026-10-15T09:30:00Z",
"data": {
"payment": {
"id": 5121,
"type": "withdrawal",
"status": "paid",
"player_id": 1042,
"amount": 5000,
"currency": "EUR",
"payment_method": "visa-mastercard",
"external_reference": "wd_20261015_001",
"info": "Visa **** 4242",
"reject_reason": null,
"is_reversed": false,
"created_at": "2026-10-15T09:30:00Z",
"paid_at": "2026-10-15T09:30:00Z",
"rejected_at": null,
"cancelled_at": null,
"updated_at": "2026-10-15T09:30:00Z"
},
"player": {
"id": 1042,
"external_id": "op-58213",
"username": "jane_doe"
}
}
}
withdrawal.rejected
Вывод отклонён, сумма возвращена
{
"id": "evt_01jaq3w8z6x4t2v9r7p5n3m1k0",
"type": "withdrawal.rejected",
"api_version": "v1",
"created_at": "2026-10-15T09:30:00Z",
"data": {
"payment": {
"id": 5121,
"type": "withdrawal",
"status": "rejected",
"player_id": 1042,
"amount": 5000,
"currency": "EUR",
"payment_method": "visa-mastercard",
"external_reference": "wd_20261015_001",
"info": "Visa **** 4242",
"reject_reason": "Proof of address required",
"is_reversed": false,
"created_at": "2026-10-15T09:30:00Z",
"paid_at": null,
"rejected_at": "2026-10-15T09:30:00Z",
"cancelled_at": null,
"updated_at": "2026-10-15T09:30:00Z"
},
"player": {
"id": 1042,
"external_id": "op-58213",
"username": "jane_doe"
}
}
}
bonus.granted
Игроку начислен бонус
{
"id": "evt_01jaq3w8z6x4t2v9r7p5n3m1k0",
"type": "bonus.granted",
"api_version": "v1",
"created_at": "2026-10-15T09:30:00Z",
"data": {
"bonus": {
"id": 9001,
"player_id": 1042,
"bonus_id": 12,
"name": "Welcome Bonus 100%",
"type": "wagering_bonus",
"state": "activated",
"result": "none",
"amount": 20000,
"currency": "EUR",
"wager_requirement": 700000,
"wagered_amount": 175000,
"wagering_progress": 25,
"converted_amount": 0,
"forfeited_amount": 0,
"max_bet": 500,
"started_at": "2026-10-15T09:30:00Z",
"expires_at": "2026-11-14T09:30:00Z",
"finished_at": null,
"created_at": "2026-10-15T09:30:00Z"
},
"player": {
"id": 1042,
"external_id": "op-58213",
"username": "jane_doe"
}
}
}
bonus.completed
Бонус завершён (конвертирован, проигран, истёк или отменён)
{
"id": "evt_01jaq3w8z6x4t2v9r7p5n3m1k0",
"type": "bonus.completed",
"api_version": "v1",
"created_at": "2026-10-15T09:30:00Z",
"data": {
"bonus": {
"id": 9001,
"player_id": 1042,
"bonus_id": 12,
"name": "Welcome Bonus 100%",
"type": "wagering_bonus",
"state": "finished",
"result": "paid",
"amount": 20000,
"currency": "EUR",
"wager_requirement": 700000,
"wagered_amount": 700000,
"wagering_progress": 100,
"converted_amount": 18250,
"forfeited_amount": 0,
"max_bet": 500,
"started_at": "2026-10-15T09:30:00Z",
"expires_at": "2026-11-14T09:30:00Z",
"finished_at": "2026-10-20T18:02:11Z",
"created_at": "2026-10-15T09:30:00Z"
},
"player": {
"id": 1042,
"external_id": "op-58213",
"username": "jane_doe"
}
}
}
kyc.status_changed
Изменился статус KYC игрока
{
"id": "evt_01jaq3w8z6x4t2v9r7p5n3m1k0",
"type": "kyc.status_changed",
"api_version": "v1",
"created_at": "2026-10-15T09:30:00Z",
"data": {
"player": {
"id": 1042,
"external_id": "op-58213",
"username": "jane_doe"
},
"previous_status": "pending_review",
"status": "verified",
"is_verified": true
}
}
compliance.alert_created
Риск-движок создал сигнал комплаенса (AML)
{
"id": "evt_01jaq3w8z6x4t2v9r7p5n3m1k0",
"type": "compliance.alert_created",
"api_version": "v1",
"created_at": "2026-10-15T09:30:00Z",
"data": {
"alert": {
"id": 77,
"rule": "large_deposit",
"severity": "high",
"status": "open",
"created_at": "2026-10-15T09:30:00Z"
},
"player": {
"id": 1042,
"external_id": "op-58213",
"username": "jane_doe"
}
}
}
Доставка и повторы
- Доставки отправляются сразу после фиксации изменения. Ответьте любым статусом 2xx в течение 5 секунд; перенаправления не выполняются.
- Любой другой ответ, тайм-аут или ошибка соединения повторяются с экспоненциальной задержкой — через 1, 2, 4, 8, 16, 32 и 64 минуты — всего до 8 попыток, после чего доставка помечается неудачной. Сотрудники могут повторить любую доставку из журнала.
- События изменений, которые были откатены, никогда не отправляются.
- Доставки могут приходить повторно и не по порядку. Устраняйте дубли по id события и упорядочивайте по created_at или состоянию ресурса.
Проверка подписи
Вычисляйте HMAC от сырого тела запроса ровно в полученном виде, сравнивайте за постоянное время и отклоняйте метки времени старше пяти минут:
<?php
$secret = getenv('KINETX_WEBHOOK_SECRET'); // whsec_…
$body = file_get_contents('php://input'); // the raw body, before any JSON parsing
$timestamp = $_SERVER['HTTP_X_KINETX_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_KINETX_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $timestamp.'.'.$body, $secret);
if (! hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
// Deliveries can repeat: process each event id once.
if (! already_processed($event['id'])) {
handle($event['type'], $event['data']);
}
http_response_code(204);
import crypto from 'node:crypto';
import express from 'express';
const app = express();
// Keep the raw body: the signature covers the exact bytes received.
app.post('/webhooks/kinetx', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-KinetX-Timestamp') ?? '';
const signature = req.get('X-KinetX-Signature') ?? '';
const expected = crypto
.createHmac('sha256', process.env.KINETX_WEBHOOK_SECRET)
.update(`${timestamp}.${req.body}`)
.digest('hex');
const valid = signature.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
&& Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
if (!valid) return res.sendStatus(400);
const event = JSON.parse(req.body);
queueForProcessing(event); // dedupe on event.id, answer fast
res.sendStatus(204);
});
Рекомендации
- Отвечайте быстро и обрабатывайте асинхронно (поставьте событие в очередь, ответьте 204).
- Считайте вебхуки уведомлениями: при сомнении запросите текущее состояние через API (например, GET /api/v1/operator/payments/{payment}).
- Подписывайте каждый адрес только на нужные ему события и меняйте секрет при подозрении на утечку.