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
X-Koi-Signature = hex( HMAC_SHA256( key = access_token, message = url + raw_body ) )| Part | Value |
|---|---|
access_token | Your current access token. |
url | The endpoint URL exactly as registered in the Back Office, including scheme, host, path and query string. Not the URL your proxy sees. |
raw_body | The request body bytes exactly as received, before any JSON parsing. |
| Output | Lowercase hexadecimal, 64 characters. |
Node.js
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
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
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.
| Input | Value |
|---|---|
| Access token | test-token |
| URL | https://wallet.example-operator.com/koi/register |
| Body | {"user_id":"player-123","amount":5,"action":"bet"} |
| Signature | 6c80b10813ba301a271cb3d13c27ce3ceba64e6ffaa39608f11ad6dc3e1faa1a |
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.