Asobi provides server-side receipt validation for Apple App Store and Google Play purchases. All IAP endpoints require an authenticated session.
Client-side receipt validation can be spoofed. Always validate purchases on the server before granting items, currency, or premium features to a player.
Validates signed transactions from StoreKit 2. The game client sends the JWS (JSON Web Signature) string obtained after a purchase.
POST /api/v1/iap/apple
curl -X POST http://localhost:8080/api/v1/iap/apple \
-H 'Authorization: Bearer <session_token>' \
-H 'Content-Type: application/json' \
-d '{"signed_transaction": "eyJhbGciOi..."}'{
"product_id": "com.example.game.gems_100",
"transaction_id": "2000000123456789",
"original_transaction_id": "2000000123456789",
"purchase_date": 1711700000000,
"expires_date": null,
"quantity": 1,
"type": "Consumable",
"valid": true
}{asobi, [
{apple_bundle_id, <<"com.example.game">>}
]}The apple_bundle_id must match your app's bundle identifier. Transactions
with a mismatched bundle ID are rejected.
After a successful validation, grant the purchase to the player using the economy system:
case asobi_iap:verify_apple(SignedTransaction) of
{ok, #{product_id := ProductId, valid := true}} ->
%% Map product ID to in-game currency/items
grant_purchase(PlayerId, ProductId);
{ok, #{valid := false}} ->
%% Subscription expired or purchase invalid
{error, expired};
{error, Reason} ->
{error, Reason}
end.Validates purchases using the Google Play Developer API. The game client sends the product ID and purchase token obtained from Google Play Billing.
POST /api/v1/iap/google
curl -X POST http://localhost:8080/api/v1/iap/google \
-H 'Authorization: Bearer <session_token>' \
-H 'Content-Type: application/json' \
-d '{
"product_id": "gems_100",
"purchase_token": "opaque-token-from-google-play..."
}'{
"product_id": "gems_100",
"order_id": "GPA.1234-5678-9012-34567",
"purchase_time": 1711700000000,
"consumption_state": 0,
"acknowledged": false,
"valid": true
}Google Play validation requires a service account with the
androidpublisher scope.
- Create a service account in Google Cloud Console
- Grant it access in Google Play Console → API access
- Download the JSON key file
{asobi, [
{google_package_name, <<"com.example.game">>},
{google_service_account_key, <<"/path/to/service-account.json">>}
]}consumptionState |
Meaning |
|---|---|
0 |
Not consumed |
1 |
Consumed |
acknowledged |
Meaning |
|---|---|
false |
Not yet acknowledged |
true |
Acknowledged |
// After a StoreKit 2 purchase (Apple)
string signedTransaction = storeKit.Transaction.JwsRepresentation;
var result = await asobi.IAP.VerifyApple(signedTransaction);
if (result.Valid) {
// Purchase verified, items granted server-side
}
// After a Google Play purchase
string purchaseToken = purchase.PurchaseToken;
var result = await asobi.IAP.VerifyGoogle("gems_100", purchaseToken);
if (result.Valid) {
// Purchase verified
}# Apple
var result = await asobi.iap.verify_apple(signed_transaction)
if result.valid:
print("Purchase verified: ", result.product_id)
# Google
var result = await asobi.iap.verify_google("gems_100", purchase_token)
if result.valid:
print("Purchase verified: ", result.order_id)// Apple
final result = await asobi.iap.verifyApple(signedTransaction);
if (result.valid) {
// Grant items
}
// Google
final result = await asobi.iap.verifyGoogle('gems_100', purchaseToken);
if (result.valid) {
// Grant items
}- Always validate receipts server-side, never trust the client alone
- Check the
product_idmatches what you expect before granting items - For Apple subscriptions, check
expires_dateandvalidtogether - For Google, acknowledge purchases after granting to prevent refund abuse
- Log all IAP validations for audit and dispute resolution
- Authentication -- auth methods and provider linking
- Economy -- wallets, currencies, and store
- REST API -- full API reference