IAP Gateway — Android
IAP Gateway is designed for co-published games that run their own game server and want full control over item delivery. Alogame only handles receipt verification with Google Play — your game server decides when and how to grant items to the player.
| Alogame SDK IAP (standard) | IAP Gateway | |
|---|---|---|
| Android client | OEGPayment.purchaseAndVerify() | Google Play Billing directly |
| Receipt verification | Alogame Backend calls Google | Alogame Backend (triggered by your game server) |
| Item delivery | Alogame calls webhook on your server | Your game server handles it after receiving the result |
| Best for | Games using the full Alogame SDK | Co-pub games with their own backend that want full control |
Before you start
Contact Alogame to receive:
client_idandclient_secret— used by your game server to authenticate with Alogame IAP Gateway- Endpoints:
- Development / Sandbox:
https://api-sdk.dev.alogame.vn/iap/gateway/verify - Production:
https://api-sdk.alogame.vn/iap/gateway/verify
- Development / Sandbox:
client_secret is issued only once. Save it securely immediately — it cannot be retrieved again.
How it works
Step 1 — Android app retrieves the purchase token
Use the Google Play Billing Library to initiate a purchase and retrieve the purchaseToken:
// build.gradle (app)
implementation("com.android.billingclient:billing:8.0.0")
It exposes billing-ktx:8.0.0 as an api dependency, so Billing is already on your classpath and this line is redundant. Declaring an older version has no effect either — Gradle resolves to the highest version in the graph.
import com.android.billingclient.api.*
class PurchaseManager(private val activity: Activity) : PurchasesUpdatedListener {
private var billingClient: BillingClient = BillingClient.newBuilder(activity)
.setListener(this)
.enablePendingPurchases(
PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()
)
.build()
fun launchPurchaseFlow(productId: String) {
val productDetails = ... // obtained via queryProductDetailsAsync()
val productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build()
val params = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(listOf(productDetailsParams))
.build()
billingClient.launchBillingFlow(activity, params)
}
override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
if (result.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
val purchaseToken = purchase.purchaseToken
// Send purchaseToken to your game server
sendToGameServer(purchaseToken, purchase.products.first())
}
}
}
}
purchaseToken is the receipt you pass to Alogame Gateway. It is a long opaque string issued by Google Play — do not parse or truncate it.
Step 2 — App sends purchase token to your game server
This is an internal API between your Android app and your game server — design it however you like. Example:
data class VerifyRequest(
val purchaseToken: String,
val productId: String,
val playerId: String,
val serverId: String
)
suspend fun sendToGameServer(purchaseToken: String, productId: String) {
val body = VerifyRequest(
purchaseToken = purchaseToken,
productId = productId,
playerId = currentUser.id,
serverId = currentServer.id
)
// POST to your game server — your server calls OEG IAP Gateway
}
Step 3 — Game server calls Alogame IAP Gateway
Your game server uses the client_id and client_secret received from OEG.
Endpoint
POST https://api-sdk.dev.alogame.vn/iap/gateway/verify (development)
POST https://api-sdk.alogame.vn/iap/gateway/verify (production)
Request signing
Every request must include three authentication headers signed with HMAC-SHA256:
X-Client-ID: <client_id>
X-Timestamp: <unix_timestamp_seconds>
X-Signature: <hmac_hex>
Signature algorithm:
body_hash = SHA256(raw_request_body_bytes) # hex string
message = "{client_id}.{timestamp}.{body_hash}"
signature = HMAC-SHA256(client_secret, message) # hex string
Replay protection: Alogame rejects requests where |server_time − X-Timestamp| > 300 seconds (5-minute window).
Node.js example:
import { createHmac, createHash } from 'crypto';
const body = JSON.stringify({
receipt: '<purchaseToken from Google Play>',
product_id: 'gems100',
os_id: 1, // 1 = Android, 2 = iOS
package: 'com.yourcompany.yourgame',
player_id: 'player-001',
character_id: 'char-001',
server_id: 'server-1',
game_version: '1.0.0',
});
const timestamp = String(Math.floor(Date.now() / 1000));
const bodyHash = createHash('sha256').update(body).digest('hex');
const message = `${CLIENT_ID}.${timestamp}.${bodyHash}`;
const signature = createHmac('sha256', CLIENT_SECRET).update(message).digest('hex');
const res = await fetch('https://api-sdk.alogame.vn/iap/gateway/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-ID': CLIENT_ID,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body,
});
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
receipt | string | Yes | purchaseToken from Google Play Billing |
product_id | string | Yes | Product ID (e.g. gems100) — required as the lookup key for the Google Play Developer API; Alogame cross-validates this against the Google response |
os_id | integer | Yes | 1 = Android, 2 = iOS |
package | string | Yes | Android package name (e.g. com.yourcompany.yourgame) |
player_id | string | No | Player ID for your records |
character_id | string | No | Character ID |
server_id | string | No | Game server ID |
game_version | string | No | App version string |
package is required for AndroidAlogame passes package to the Google Play Developer API to locate the purchase. Requests without a package field will be rejected.
Response
Success
{
"success": true,
"transaction_id": "GPA.1234-5678-9012-34567",
"product_id": "gems100",
"receipt_info": {
"platform": "android",
"environment": null,
"quantity": 1,
"purchase_date_ms": 1716389123000,
"purchase_state": 0,
"region_code": "VN",
"acknowledgement_state": 0,
"order_id": "GPA.1234-5678-9012-34567"
}
}
receipt_info fields (Android)
| Field | Type | Description |
|---|---|---|
platform | string | Always "android" |
environment | null | Always null — Google does not distinguish sandbox/production in this API |
quantity | integer | Number of units purchased |
purchase_date_ms | integer | Purchase timestamp in milliseconds (Unix epoch) |
purchase_state | integer | 0 = PURCHASED, 1 = CANCELED, 2 = PENDING |
region_code | string | ISO 3166-1 alpha-2 country code where the purchase was made (e.g. "VN") |
acknowledgement_state | integer | 0 = not acknowledged, 1 = acknowledged |
order_id | string | Google Play order ID — same as transaction_id |
Error
{ "success": false, "error": "ERROR_CODE", "message": "human-readable detail" }
| Case | HTTP | error |
|---|---|---|
| Verification successful | 200 | — |
| Receipt already verified | 409 | DUPLICATE_TRANSACTION |
product_id in request doesn't match Google receipt | 422 | PRODUCT_MISMATCH |
| Receipt rejected by Google | 422 | VERIFICATION_FAILED |
| Google server unreachable / unexpected error | 502 | VERIFIER_ERROR |
| Missing required fields | 400 | MISSING_FIELDS |
os_id value not 1 or 2 | 400 | INVALID_OS_ID |
| Invalid credentials or signature | 401 | UNAUTHORIZED |
success: true→ grant the item. Consumable items should then be consumed viaBillingClient.consumeAsync()to allow re-purchase.4xx→ request was wrong (bad fields, missingpackage, auth failure). Fix the request; do not retry blindly.409 DUPLICATE_TRANSACTION→ Alogame already verified this purchase token (common on retry after crash). Check if the item was already granted; do not re-grant.422 PRODUCT_MISMATCH→ theproduct_idyou sent does not match the product recorded by Google. This is a sign of a receipt swap attack or a client bug — do not grant the item.422 VERIFICATION_FAILED→ Google rejected the purchase token (purchaseState != 0, package mismatch, SA not configured). Do not grant the item.5xx→ upstream or server error. Safe to retry with exponential back-off.
Integration checklist
- Received
client_idandclient_secretfrom Alogame — stored securely server-side - Alogame configured with Google service account credentials for this game (required to call Google Play Developer API)
- Android: app retrieves
purchaseTokenfromPurchase.getPurchaseToken() - Android: consumable items consumed via
consumeAsync()after server confirms success - Game server: signs every request with HMAC-SHA256 (see Step 3)
- Game server: includes
packagefield (Android package name) in every request - Game server: checks
successfield — not just HTTP status — before granting item - Game server: treats
409 DUPLICATE_TRANSACTIONas "already verified" — checks local state, does not re-grant - Game server: treats
422 VERIFICATION_FAILEDas "do not grant" — purchase was rejected by Google - Game server: retries with exponential back-off on
5xx(VERIFIER_ERROR) - Game server: stores
transaction_id(= Googleorder_id) for idempotent delivery — prevents double-granting