Skip to content
KinetX
Operator API v1

Webhooks

Get notified the moment something happens on the platform — registrations, payments, bonuses, KYC decisions and compliance alerts — with signed HTTPS callbacks to your servers.

Overview

Staff add webhook endpoints in the backoffice (Tools → API & Webhooks → Webhooks): an HTTPS URL, the events it subscribes to, and a signing secret shown once. A "Send test event" button posts a ping, and the delivery log shows every attempt with the response, ready to retry.

Events fire for everything that happens on your operator account — whether it came through the API, your KinetX-hosted website, the backoffice or a scheduled job.

Every delivery is a POST with a JSON body and these headers:

X-KinetX-EventEvent type, e.g. deposit.paid.
X-KinetX-Event-IdUnique event id (also in the body); use it to process each event once.
X-KinetX-DeliveryDelivery id, the same for every retry of this delivery.
X-KinetX-TimestampUnix time of this attempt, part of the signature.
X-KinetX-SignatureHex HMAC-SHA256 of "{timestamp}.{raw body}" keyed with the endpoint secret.

Events

The body always has the same envelope — id, type, api_version, created_at and the event data. Data objects are the same resources the API returns.

player.registered A player registered (website, backoffice or API)
json
{
    "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 A deposit was paid and credited to the wallet
json
{
    "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 A player requested a withdrawal (the amount is held)
json
{
    "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 A withdrawal was paid out
json
{
    "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 A withdrawal was rejected and the amount refunded
json
{
    "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 A bonus was granted to a player
json
{
    "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 A bonus finished (converted, lost, expired or cancelled)
json
{
    "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 The KYC status of a player changed
json
{
    "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 The risk engine raised a compliance (AML) alert
json
{
    "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"
        }
    }
}

Delivery & retries

  • Deliveries are sent right after the change is committed. Answer with any 2xx status within 5 seconds; redirects are not followed.
  • Any other answer, a timeout or a connection error is retried with exponential backoff — after 1, 2, 4, 8, 16, 32 and 64 minutes — up to 8 attempts in total, then the delivery is marked failed. Staff can retry any delivery from the log.
  • Events of a change that was rolled back are never sent.
  • Deliveries can arrive more than once and out of order. Deduplicate on the event id and use created_at or the resource state to order them.

Verifying signatures

Compute the HMAC over the raw request body exactly as received, compare it in constant time, and reject timestamps older than five minutes:

<?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);

Best practices

  • Acknowledge fast and process asynchronously (queue the event, answer 204).
  • Treat webhooks as notifications: when in doubt, fetch the current state from the API (e.g. GET /api/v1/operator/payments/{payment}).
  • Subscribe each endpoint only to the events it needs, and rotate the secret if it may have leaked.