XMDT Server Integration
Your server makes exactly one call. It never carries or receives a player's name, date of birth, phone number, or OTP code.
Everything on this page describes the dev environment (api-xmdt.dev.alogame.vn). Production has not been provisioned yet — do not point a live build at it.
| Call | When | Why |
|---|---|---|
POST /xmdt/session | Whenever your client needs to open the XMDT screen, and again at any point you need to trust whether a player has verified | Mints a short-lived token for the SDK — your HMAC secret never reaches a device. Its response also carries xmdtCompleted, read straight from Alogame, never relayed through the player's device — that's your authoritative answer. |
Base URL (dev): https://api-xmdt.dev.alogame.vn
Getting a secret
Contact your Alogame Operations contact to enable XMDT for your gameId. You'll be given a secret_key once, at the moment it's generated — it cannot be retrieved again, so store it immediately in your own secrets management, not in a config file that ends up in version control.
Signing requests
This call uses the following signature scheme, and it's simpler than Alogame's payment contracts: no timestamp, no replay window, no field sorting.
X-Signature: HMAC-SHA256(secret_key, raw_request_body), lowercase hex
The signed message is the raw JSON body bytes exactly as sent — not a re-serialized or sorted version of it.
If your framework parses the JSON and you sign the re-serialized form, key order, spacing, and number formatting can all differ from what you actually send, and the signature will not match. Capture the raw body before your JSON parser consumes it (see the payment API authentication guide for language-specific examples of doing this correctly — same principle applies here even though the signing formula itself is different).
const crypto = require("crypto");
function signXmdtBody(rawBody, secret) {
return crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
}
import hmac, hashlib
def sign_xmdt_body(raw_body: bytes, secret: str) -> str:
return hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
function signXmdtBody(string $rawBody, string $secret): string {
return hash_hmac('sha256', $rawBody, $secret);
}
Send it as the X-Signature header, Content-Type: application/json, on the call below. There is no separate service-id header to set — gameId in the request body is what's verified against.
POST /xmdt/session — mint a session token
Call this whenever your client is about to open the XMDT screen (AlogameKycSdk.show()).
Request
{
"gameId": "your_game_id",
"username": "player_account_name",
"serverId": "server_01",
"roleId": "character_123"
}
| Field | Required | Notes |
|---|---|---|
gameId | Yes | Your registered game code |
username | Yes | Your own stable per-player identifier — this, not a device ID, is what ties a player to their XMDT record |
serverId | No | Omit if your game has no server selection at this point |
roleId | No | Omit if the player hasn't picked a character yet |
BODY='{"gameId":"your_game_id","username":"player_account_name"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -X POST https://api-xmdt.dev.alogame.vn/xmdt/session \
-H "Content-Type: application/json" \
-H "X-Signature: $SIG" \
-d "$BODY"
Response
{
"sessionToken": "opaque-token-hand-this-to-your-client",
"expiresAt": "2026-08-07T12:15:00Z",
"xmdtRequired": true,
"xmdtCompleted": false
}
| Field | Meaning |
|---|---|
sessionToken | Pass this straight to your client — it's what AlogameKycSdk.setGameRole() needs. Valid for 15 minutes. |
xmdtRequired | Whether the SDK's form shows a close button. Set per-game by Alogame Operations; you don't configure this per-call. |
xmdtCompleted | This player already has a complete XMDT record — your client can skip calling show() entirely if you want to avoid a no-op screen open. |
The token is single-use for the collect step and expires in 15 minutes — mint a fresh one for each show() call rather than caching and reusing one across sessions.
Using this as the authoritative check
Do not gate anything on the SDK's onResult callback alone — that comes from the player's device, and a modified client can report success without ever verifying anything. At the point you actually need to trust the answer (letting a player into a match, for example), call this same endpoint again and read xmdtCompleted off the response. There's no separate status route: the field is authoritative because it comes straight from Alogame over the same signed, server-to-server call — never relayed through the player's device.
Cache a true result on your side rather than minting again on every login — a completed record doesn't become incomplete again, and minting is rate-limited per player (see Errors below).
Errors
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed JSON, or gameId/username missing |
| 401 | missing_headers | No X-Signature header sent |
| 401 | invalid_signature | Signature doesn't match — check you signed the exact raw body |
| 403 | unknown_service | gameId is not registered or is inactive |
| 409 | xmdt_disabled | XMDT is not (yet) enabled for your gameId — contact Operations |
| 409 | email_required_unsupported | Your game has an email-verification requirement configured that this SDK flow doesn't support — contact Operations |
| 429 | rate_limited | Too many mint requests for this player/IP in a short window — back off and retry, no detail on which limit was hit |
| 500 | internal_error | Retry with backoff |
All error responses share the shape {"code": "...", "message": "..."}.