Skip to main content

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.

When to use IAP Gateway instead of Alogame SDK IAP?
Alogame SDK IAP (standard)IAP Gateway
Android clientOEGPayment.purchaseAndVerify()Google Play Billing directly
Receipt verificationAlogame Backend calls GoogleAlogame Backend (triggered by your game server)
Item deliveryAlogame calls webhook on your serverYour game server handles it after receiving the result
Best forGames using the full Alogame SDKCo-pub games with their own backend that want full control

Before you start

Contact Alogame to receive:

  • client_id and client_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
warning

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")
Already using the Alogame SDK?

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())
}
}
}
}
note

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

FieldTypeRequiredDescription
receiptstringYespurchaseToken from Google Play Billing
product_idstringYesProduct ID (e.g. gems100) — required as the lookup key for the Google Play Developer API; Alogame cross-validates this against the Google response
os_idintegerYes1 = Android, 2 = iOS
packagestringYesAndroid package name (e.g. com.yourcompany.yourgame)
player_idstringNoPlayer ID for your records
character_idstringNoCharacter ID
server_idstringNoGame server ID
game_versionstringNoApp version string
package is required for Android

Alogame 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)

FieldTypeDescription
platformstringAlways "android"
environmentnullAlways null — Google does not distinguish sandbox/production in this API
quantityintegerNumber of units purchased
purchase_date_msintegerPurchase timestamp in milliseconds (Unix epoch)
purchase_stateinteger0 = PURCHASED, 1 = CANCELED, 2 = PENDING
region_codestringISO 3166-1 alpha-2 country code where the purchase was made (e.g. "VN")
acknowledgement_stateinteger0 = not acknowledged, 1 = acknowledged
order_idstringGoogle Play order ID — same as transaction_id

Error

{ "success": false, "error": "ERROR_CODE", "message": "human-readable detail" }
CaseHTTPerror
Verification successful200
Receipt already verified409DUPLICATE_TRANSACTION
product_id in request doesn't match Google receipt422PRODUCT_MISMATCH
Receipt rejected by Google422VERIFICATION_FAILED
Google server unreachable / unexpected error502VERIFIER_ERROR
Missing required fields400MISSING_FIELDS
os_id value not 1 or 2400INVALID_OS_ID
Invalid credentials or signature401UNAUTHORIZED
How to handle errors by category
  • success: true → grant the item. Consumable items should then be consumed via BillingClient.consumeAsync() to allow re-purchase.
  • 4xx → request was wrong (bad fields, missing package, 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 → the product_id you 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_id and client_secret from 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 purchaseToken from Purchase.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 package field (Android package name) in every request
  • Game server: checks success field — not just HTTP status — before granting item
  • Game server: treats 409 DUPLICATE_TRANSACTION as "already verified" — checks local state, does not re-grant
  • Game server: treats 422 VERIFICATION_FAILED as "do not grant" — purchase was rejected by Google
  • Game server: retries with exponential back-off on 5xx (VERIFIER_ERROR)
  • Game server: stores transaction_id (= Google order_id) for idempotent delivery — prevents double-granting