Authentication
Every request Alogame sends to your server is signed. Verify the signature on every incoming request before doing any work, and reject anything that fails or is too old.
For Web Payment — Standard, Web Payment — Expub, and Mobile IAP, use HMAC-SHA256 for any new integration. Two MD5 variants exist for already-integrated titles, but they are being deprecated — do not build against them.
Mobile IAP signs requests the same way as whichever Web Payment contract your game uses — they share one signing configuration per game, because createOrder_url/exchange_url are the same registered endpoints for both (see Mobile IAP). Moving a game to HMAC-SHA256 moves both flows together; there is no way to run one on HMAC and the other on MD5.
| Algorithm | Headers | |
|---|---|---|
| HMAC-SHA256 | x-signature, x-timestamp | Use this for a new integration. |
| MD5, with timestamp | Signature | Existing integrations only — being deprecated |
| MD5, no timestamp | Signature | Existing integrations only — being deprecated |
Not sure which one your integration uses? Ask your Alogame Operations contact, or check which headers a real call from Alogame actually carries.
There is a fourth, unrelated algorithm — HMAC-SHA256 with a sorted query string — used for exactly one thing: the In-Game Top-Up signed deep link, which you build and Alogame verifies (the reverse of the three above).
Your secret_key is issued by Alogame. Never expose it in a client build, a public repository, or a log line.
HMAC-SHA256 — standard contract
Every request carries two headers:
| Header | Value |
|---|---|
x-timestamp | Unix timestamp in milliseconds |
x-signature | HMAC-SHA256(secret_key, x-timestamp + raw_request_body), lowercase hex |
The signed string is the x-timestamp value concatenated directly with the raw JSON request body — no separator, no sorting, no URL-encoding.
The signature covers the exact bytes Alogame sent. If you parse the JSON and re-serialize it before hashing, key order, spacing, and number formatting can all change, and every signature will fail.
Capture the raw body before your framework's JSON parser consumes it:
- Express:
express.json({ verify: (req, _res, buf) => { req.rawBody = buf } }) - Laravel / PHP:
file_get_contents('php://input') - Spring:
ContentCachingRequestWrapper
Reject the request if:
- Computed signature ≠
x-signature |now − x-timestamp| > 600000ms (10-minute replay window — note the header is in milliseconds, unlike the expub contract which uses seconds)
Compare with a constant-time function (hash_equals, hmac.compare_digest, crypto.timingSafeEqual), not ==.
function verifySignature(string $rawBody, string $timestamp, string $secret, string $received): bool {
$expected = hash_hmac('sha256', $timestamp . $rawBody, $secret);
return hash_equals($expected, $received);
}
$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$received = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
import hmac, hashlib
def verify_signature(raw_body: bytes, timestamp: str, secret: str, received: str) -> bool:
expected = hmac.new(
secret.encode(),
timestamp.encode() + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, received)
const crypto = require("crypto");
function verifySignature(rawBody, timestamp, secret, received) {
const expected = crypto
.createHmac("sha256", secret)
.update(timestamp + rawBody)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(received);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
MD5, with timestamp
Being deprecated for Web Payment — Standard, Web Payment — Expub, and Mobile IAP alike — only for titles already integrated on it. Use HMAC-SHA256 for a new integration. Mobile IAP is not a fixed exception: it signs with whatever algorithm the game's Web Payment contract is configured for, since both share createOrder_url/exchange_url.
Every request from Alogame includes a Signature header. Verify it on every incoming request before processing.
Signature algorithm
Signature = MD5(sorted_query_string(body) + secret_key + timestamp)
Steps:
- Convert the JSON body to query-string format:
key=value&key2=value2 - Sort keys alphabetically
- Append your
secret_key(provided by Alogame — never expose this) - Append the
timestampvalue from the request body (Unix seconds) - MD5-hash the resulting string
Example — body { "userId": "abc-123", "timestamp": 1678886400 }:
# Sorted query string
timestamp=1678886400&userId=abc-123
# Append secret + timestamp
timestamp=1678886400&userId=abc-123mysecret1678886400
# Result
Signature: MD5("timestamp=1678886400&userId=abc-123mysecret1678886400")
Reject the request if:
- Computed signature ≠
Signatureheader |server_time − timestamp| > 600seconds (10-minute replay window)
Verification samples
fun verifySignature(body: Map<String, String>, secret: String, received: String): Boolean {
val sorted = body.entries.sortedBy { it.key }.joinToString("&") { "${it.key}=${it.value}" }
val timestamp = body["timestamp"] ?: return false
val raw = "$sorted$secret$timestamp"
val expected = MessageDigest.getInstance("MD5")
.digest(raw.toByteArray())
.joinToString("") { "%02x".format(it) }
return expected == received
}
function verifySignature(array $body, string $secret, string $received): bool {
ksort($body);
$sorted = http_build_query($body);
$timestamp = $body['timestamp'] ?? '';
return md5($sorted . $secret . $timestamp) === $received;
}
import hashlib
from urllib.parse import urlencode
def verify_signature(body: dict, secret: str, received: str) -> bool:
sorted_body = dict(sorted(body.items()))
timestamp = str(body.get("timestamp", ""))
raw = urlencode(sorted_body) + secret + timestamp
return hashlib.md5(raw.encode()).hexdigest() == received
MD5, no timestamp
Being deprecated — only for Web Payment — Standard or Web Payment — Expub titles already integrated on it; never used for Mobile IAP. Use HMAC-SHA256 for a new integration. Same idea as MD5, with timestamp above, minus the timestamp: no timestamp field is sent in the request body, and none is folded into the signed string.
Signature algorithm
Signature = MD5(sorted_query_string(body) + secret_key)
Steps:
- Convert the JSON body to query-string format:
key=value&key2=value2 - Sort keys alphabetically
- Append your
secret_key(provided by Alogame — never expose this) - MD5-hash the resulting string
Example — body { "uid": "100002078" }:
# Sorted query string
uid=100002078
# Append secret
uid=100002078mysecret
# Result
Signature: MD5("uid=100002078mysecret")
Reject the request if:
- Computed signature ≠
Signatureheader
With no timestamp traveling with the request, this scheme has no built-in replay window — a captured, valid request/signature pair stays valid indefinitely. It exists for a small number of titles already integrated this way before HMAC-SHA256 was available; prefer HMAC-SHA256 for any new integration.
Verification samples
fun verifySignature(body: Map<String, String>, secret: String, received: String): Boolean {
val sorted = body.entries.sortedBy { it.key }.joinToString("&") { "${it.key}=${it.value}" }
val raw = "$sorted$secret"
val expected = MessageDigest.getInstance("MD5")
.digest(raw.toByteArray())
.joinToString("") { "%02x".format(it) }
return expected == received
}
function verifySignature(array $body, string $secret, string $received): bool {
ksort($body);
$sorted = http_build_query($body);
return md5($sorted . $secret) === $received;
}
import hashlib
from urllib.parse import urlencode
def verify_signature(body: dict, secret: str, received: str) -> bool:
sorted_body = dict(sorted(body.items()))
raw = urlencode(sorted_body) + secret
return hashlib.md5(raw.encode()).hexdigest() == received
HMAC-SHA256 — sorted query string, in-game deep link
Used for exactly one flow: the signed deep link in In-Game Top-Up — the URL your own backend builds and hands to your game client to open the embedded top-up page directly, for titles that aren't routing through the Alogame SDK's native call. Every other contract on this site uses one of the two algorithms above; this one only applies to that flow, and it is verified by Alogame, not by you — you only need to build it, using the same secret_key issued for your check_uid/create_order contract.
The signed string is a sorted query string, built the same way as the MD5 contract above — but hashed with HMAC-SHA256 and the result is uppercase hex (unlike the standard contract's lowercase).
Signed fields — sorted alphabetically, joined as key=value&key=value:
| Field | Required? |
|---|---|
uid | always |
product_id | always |
deeplink_ts | always — Unix milliseconds |
order_code | only if your flow uses one |
server_id | only for multi-server titles |
return_url | only if you want the player sent back somewhere after payment |
Omit a field entirely rather than sending it empty — an optional field that's present changes what gets signed.
deeplink_sig = HMAC-SHA256(secret_key, sorted_query_string(fields)).toUpperCase()
Example — fields { uid: "100002078", product_id: "pkg_100", deeplink_ts: 1755500000000 }:
# Sorted query string
deeplink_ts=1755500000000&product_id=pkg_100&uid=100002078
# Signature
deeplink_sig = HMAC-SHA256(secret_key, "deeplink_ts=1755500000000&product_id=pkg_100&uid=100002078").toUpperCase()
Alogame rejects the link if (for your reference — you can't retry a rejection, so get this right before shipping):
- Computed signature ≠
deeplink_sig |now − deeplink_ts| > 300000ms — a 5-minute replay window, narrower than the 10-minute window on the webhook contracts above. Generatedeeplink_tsright before opening the link, not ahead of time.
const crypto = require("crypto");
const querystring = require("querystring");
function buildDeepLinkSignature(fields, secretKey) {
const sorted = Object.fromEntries(
Object.keys(fields).sort().map((k) => [k, fields[k]])
);
const raw = querystring.unescape(querystring.stringify(sorted));
return crypto.createHmac("sha256", secretKey).update(raw).digest("hex").toUpperCase();
}
const fields = { uid: "100002078", product_id: "pkg_100", deeplink_ts: Date.now() };
const sig = buildDeepLinkSignature(fields, secretKey);
function buildDeepLinkSignature(array $fields, string $secretKey): string {
ksort($fields);
$raw = urldecode(http_build_query($fields));
return strtoupper(hash_hmac('sha256', $raw, $secretKey));
}
$fields = ['uid' => '100002078', 'product_id' => 'pkg_100', 'deeplink_ts' => (int) round(microtime(true) * 1000)];
$sig = buildDeepLinkSignature($fields, $secretKey);