Skip to content
KOI Games Developer

Wallet callbacks

Signature verification

Every callback carries X-Koi-Signature. Verify it before reading the body, and reject the request if it does not match.

Algorithm

text
X-Koi-Signature = hex( HMAC_SHA256( key = access_token, message = url + raw_body ) )
PartValue
access_tokenYour current access token.
urlThe endpoint URL exactly as registered in the Back Office, including scheme, host, path and query string. Not the URL your proxy sees.
raw_bodyThe request body bytes exactly as received, before any JSON parsing.
OutputLowercase hexadecimal, 64 characters.

Node.js

javascript
import { createHmac, timingSafeEqual } from 'node:crypto'
import express from 'express'

const ACCESS_TOKEN = process.env.KOI_ACCESS_TOKEN
const REGISTERED_URL = 'https://wallet.example-operator.com/koi/register'

const app = express()

app.post('/koi/register', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = createHmac('sha256', ACCESS_TOKEN)
    .update(REGISTERED_URL)
    .update(req.body)
    .digest()
  const received = Buffer.from(String(req.get('X-Koi-Signature') ?? ''), 'hex')

  if (received.length !== expected.length || !timingSafeEqual(received, expected)) {
    return res.status(401).json({ status: 'DENIED', code: 'BET_DENIED' })
  }

  const bet = JSON.parse(req.body.toString('utf8'))
  // Debit idempotently by bet.provider_tx_id…
})

Python

python
import hashlib
import hmac

def is_valid(access_token: str, registered_url: str, raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(
        access_token.encode(),
        registered_url.encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature or "")

PHP

php
function koi_signature_is_valid(string $accessToken, string $registeredUrl, string $rawBody, ?string $signature): bool {
    $expected = hash_hmac('sha256', $registeredUrl . $rawBody, $accessToken);
    return is_string($signature) && hash_equals($expected, $signature);
}

$valid = koi_signature_is_valid(
    getenv('KOI_ACCESS_TOKEN'),
    'https://wallet.example-operator.com/koi/register',
    file_get_contents('php://input'),
    $_SERVER['HTTP_X_KOI_SIGNATURE'] ?? null
);

Test vector

Use this vector to check your implementation.

InputValue
Access tokentest-token
URLhttps://wallet.example-operator.com/koi/register
Body{"user_id":"player-123","amount":5,"action":"bet"}
Signature6c80b10813ba301a271cb3d13c27ce3ceba64e6ffaa39608f11ad6dc3e1faa1a

Good practice

  • Compare signatures in constant time, as in the examples.
  • Keep the access token on your servers. Never expose it to a browser or a game client.
  • Do not log the signature header, the access token or full callback bodies.
  • Because retries resend the same body, a repeated signature is expected. Rely on idempotency, not on signature uniqueness.