Instant Games

In-App Purchases (IAP)

Updated: Aug 3, 2026
Copy for LLM
In-App Purchases allow you to sell virtual goods, currency, and other digital content directly within your Instant Game.
This guide covers everything from initial setup to advanced integration patterns, including product catalog management, SDK integration, server-side verification, and testing.

How IAP works

When a player buys something in your game, the following happens:
  1. Your game calls the Instant Games SDK to display available products.
  2. The player selects a product and confirms the purchase through the Facebook payment dialog.
  3. Facebook processes the payment using the player’s saved payment method (credit card, PayPal, or other regional payment methods).
  4. Your game receives a purchase confirmation with a signed receipt.
  5. You verify the receipt server-side and deliver the purchased content.
The player never leaves your game during this process. The payment dialog is an overlay that appears within the Instant Games container, so the player stays in the game throughout the purchase.

Platform support

In-App Purchases are supported on all platforms where Instant Games are available, including iOS, Android, and the web (facebook.com). On iOS, purchases are processed through Apple’s billing system.
Regardless of platform, your game should always check for payment API availability before showing purchase UI, using FBInstant.getSupportedAPIs(). This ensures your game handles any edge cases where payments may be temporarily unavailable on a particular client or device.

Product types

The current Instant Games Digital Fulfillment Catalog (DFC) flow supports two self-serve product types:

Consumable products

Consumable products are items that can be used up and purchased again. After a player buys a consumable product, you must explicitly consume it via the SDK before the player can buy it again. Most virtual goods in games are consumables.
Examples: Coin packs, gem bundles, extra lives, energy refills, loot boxes, power-ups.

Non-consumable (durable) products

Non-consumable products are permanent, one-time purchases that persist in the player’s account indefinitely. Once purchased, a durable product cannot be bought again. You should not consume durable products.
Examples: Ad removal (“Remove Ads” upgrade), premium skins, character unlocks, full game unlocks, permanent level packs.

Subscriptions - not supported

Subscriptions are not currently supported in Instant Games.
Subscriptions are recurring purchases that charge the player on a regular schedule (weekly, monthly, or another interval). The player is billed automatically until they cancel.
Examples: Monthly VIP membership, weekly battle pass, seasonal subscription with exclusive content.

Setting up IAP

Before you can sell anything in your game, you need to complete several setup steps in the App Dashboard.

Step 1: Enable IAP for your app

  1. Go to the App Dashboard and select your app.
  2. Navigate to Instant Games in the left sidebar.
  3. Under In-App Purchases, toggle the feature to Enabled.

Step 2: Complete the IAP approval process

In-App Purchases require approval from the Facebook team before they are available to real players. To request approval:
  1. In the App Dashboard, go to Instant Games > In-App Purchases.
  2. Submit your app for IAP review. You will need to provide:
  3. The review typically takes a few business days. You will be notified of the outcome in the App Dashboard.
While you wait for approval, you can still develop and test your IAP integration using test purchases (see Testing IAP).

Step 3: Create a payout account and business

The following screenshot shows the Game Payments settings in the DFC dashboard:
Game payments settings in DFC dashboard
To receive payments from your IAP revenue, you need to set up a payout account:
  1. In the App Dashboard, go to Settings > Payments.
  2. Follow the prompts to create or link a Business entity (this is a Facebook Business account associated with your developer account).
  3. Add your bank account or other payout method.
  4. Complete any required tax documentation (W-9 for US developers, W-8BEN for international developers, or equivalent).
  5. Once your payout account is verified, you will begin receiving payments according to the payout schedule.

Product catalog management

Your game’s products are managed through the Digital Fulfillment Catalog (DFC) in the App Dashboard. This is where you create, edit, and manage every item available for purchase in your game.

Creating a product

To add a new product:
  1. In the App Dashboard, go to Instant Games > In-App Purchases > Products.
  2. Click Create Product.
  3. Fill in the product fields (described below).
  4. Save the product.

Product fields

Each product has the following fields:
FieldDescriptionExample
Product ID
A unique string identifier for this product. This is the ID you use in SDK calls. Once set, it cannot be changed. Use a descriptive, namespaced format.
coins_500, premium_unlock
Product Name
The display name shown to the player in the purchase dialog. Keep it clear and concise.
“500 Gold Coins”, “Remove Ads”
Description
A short description of the product shown to the player. Explain what they are getting.
“A bundle of 500 gold coins to spend in the shop.”
Price
The price of the product in your base currency (typically USD). Facebook automatically converts this to the player’s local currency. Prices must meet minimum price thresholds (typically $0.99 USD or equivalent).
0.99, 4.99, 9.99
Product Type
Whether this product is consumable or non-consumable (durable).
CONSUMABLE, NON_CONSUMABLE

Bulk operation limits

The App Dashboard limits how many products each bulk operation handles:
  • CSV import. A file can contain at most 20 products. If yours has more, split it into several files and import them one after another.
  • Adding products manually. You can enter at most 20 products before saving.
  • Deleting products. You can select at most 8 products for each bulk delete.
Deleting has a lower limit than adding because each deletion resolves more data per product on the server.
The limits apply per operation, not to your catalog as a whole, so you can repeat an operation as many times as you need to manage a larger catalog.
The Instant Games IAP Catalog API described below is not affected. Each request creates, updates, or deletes a single product, so there is no batch limit to observe — send one request per product.

Product catalog best practices

  • Use clear, descriptive Product IDs. You cannot change them later. Use a naming convention like category_item_quantity (e.g., currency_coins_500, powerup_shield_1).
  • Write player-friendly names and descriptions. These are shown in the Facebook payment dialog, so make them clear and appealing.
  • Offer a range of price points. Include both low-cost impulse purchases ($0.99 - $1.99) and higher-value bundles ($4.99 - $19.99+) to capture different spending levels.
  • Test your catalog thoroughly. Use test purchases to verify that every product displays correctly and delivers the right content.

Managing the catalog via API (V1)

In addition to the App Dashboard, you can create, update, and delete products programmatically using the Instant Games IAP Catalog API. This is useful for automated workflows, bulk catalog updates, or CI/CD pipelines that manage your game’s product catalog without requiring manual changes in the dashboard.

Prerequisites

All requests are authenticated as the app. The server validates that the {app_id} in the URL matches the authenticated app based on the token. If they do not match, you will receive a 403 Forbidden error.

Adding or updating a product

To create a new product or update an existing one, send a PUT request to the catalog items endpoint. If a product with the given product_id already exists, its editable fields will be updated. If it does not exist, a new product will be created.
curl -i -X PUT "https://api.facebook.com/instant-games/catalog/{app-id}/items" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {app-id}|{app-token}" \
  -H "X-API-Version: 1.0.0" \
  -d '{
    "product_id": "coins_500",
    "product_title": "500 Gold Coins",
    "product_description": "A bundle of 500 gold coins to spend in the shop.",
    "product_type": "consumable",
    "product_price_amount": "0.99"
  }'
The request body accepts the following fields:
FieldTypeRequiredDescription
product_id
string
Yes
Developer-defined unique product identifier within the app.
product_title
string
Yes
Display title shown to the player.
product_description
string
No
Display description shown to the player.
product_type
string
Yes
One of: consumable, durable.
product_price_amount
string
Yes
Decimal price string (for example, "0.99"). Must match a supported price point.
Note: The price currency code is default USD.
On success, the API returns:
204 No Content

Deleting a product

To delete an existing product, send a DELETE request with the product_id in the URL path:
curl -i -X DELETE "https://api.facebook.com/instant-games/catalog/{app-id}/items/{product-id}" \
  -H "Authorization: Bearer {app-id}|{app-token}" \
  -H "X-API-Version: 1.0.0"
Replace {product-id} with the product identifier you defined when creating the product (the same ID returned by FBInstant.payments.getCatalogAsync() in the SDK).
On success, the API returns:
204 No Content

Common API error cases

Status CodeMeaningRecommended Action
400 Bad Request
Missing or invalid fields, unsupported product_type, or invalid price point/currency combination.
Check that all required fields are present and that the price matches an allowed catalog price point.
403 Forbidden
The app_id in the URL does not match the authenticated app.
Verify that your access token corresponds to the correct app.
404 Not Found
The product_id does not exist (delete only).
Confirm the product ID is correct and that the product has not already been deleted.
5xx Server Error
A transient server-side failure.
Retry the request after a short delay.

Managing the catalog via API (V2 — Dynamic Regional Pricing Beta)

The V2 API extends the catalog management endpoints with support for Dynamic Regional Pricing. When your app is opted into dynamic pricing, you can publish per-region copies of each catalog item with different USD price points for different regions (currently united_states, apac_tier_1/apac_tier_2/apac_tier_3, latam_tier_1/latam_tier_2/latam_tier_3, and emea_tier_1/emea_tier_2/emea_tier_3).
V2 also adds a new GET method that returns the full list of catalog items for an app per region. This is useful for verifying the current state of your catalog or for syncing with an external system of record.
To use V2 catalog endpoints, you must include the X-API-Version: 2.0.0 header in all requests. Use V2 only if you are enrolled in the Dynamic Regional Pricing Beta or to use the new list endpoint.

Region semantics

The region field follows a single rule across all V2 methods:
  • If your app is opted into dynamic regional pricing, region is required on every V2 request. Omitting it returns 400 Bad Request.
  • If your app is not opted into dynamic regional pricing, please use the V1 endpoints unless you want to use the new list endpoint, region is ignored.
Valid region values:
ValueDescription
united_states
Default catalog (no regional suffix applied).
apac_tier_1
APAC tier 1 regional catalog.
apac_tier_2
APAC tier 2 regional catalog.
apac_tier_3
APAC tier 3 regional catalog.
latam_tier_1
LATAM tier 1 regional catalog.
latam_tier_2
LATAM tier 2 regional catalog.
latam_tier_3
LATAM tier 3 regional catalog.
emea_tier_1
EMEA tier 1 regional catalog.
emea_tier_2
EMEA tier 2 regional catalog.
emea_tier_3
EMEA tier 3 regional catalog.
Currency is always USD for all regions in this iteration; only the product_price_amount varies between regions.

Adding or updating a product (V2)

To create or update a region-aware catalog item, send a PUT request to the V2 items endpoint. If the underlying SKU (the product_id you supply plus the appropriate regional suffix) already exists, its editable fields are updated; otherwise a new item is created.
curl -i -X PUT "https://api.facebook.com/instant-games/catalog/{app-id}/items" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {app-id}|{app-token}" \
  -H "X-API-Version: 2.0.0" \
  -d '{
    "product_id": "coins_500",
    "product_title": "500 Gold Coins",
    "product_description": "A bundle of 500 gold coins to spend in the shop.",
    "product_type": "consumable",
    "product_price_amount": "0.49",
    "region": "apac_tier_1"
  }'
The request body accepts the following fields:
FieldTypeRequiredDescription
product_id
string
Yes
Developer-defined unique product identifier within the app. The server adds the regional suffix internally — do not include it.
product_title
string
Yes
Display title shown to the player.
product_description
string
No
Display description shown to the player.
product_type
string
Yes
One of: consumable, durable.
product_price_amount
string
Yes
Decimal price string (for example, "0.49"). Must match a supported USD price point.
region
string
Yes
See the Region semantics table above for the supported enum values. Required if the app is opted into dynamic pricing.
On success, the API returns 204 No Content.

Deleting a product (V2)

To delete a region-aware catalog item, send a DELETE request with the product_id you defined in the URL path and the region as a query parameter:
curl -i -X DELETE "https://api.facebook.com/instant-games/catalog/{app-id}/items/{product-id}?region=apac_tier_1" \
  -H "Authorization: Bearer {app-id}|{app-token}" \
  -H "X-API-Version: 2.0.0"
{product-id} is the identifier you defined when creating the product. When the app is opted into dynamic pricing, you must include region to identify which regional variant to delete.
On success, the API returns 204 No Content.

Listing products (V2)

To retrieve the catalog items for an app, send a GET request:
curl -i -X GET "https://api.facebook.com/instant-games/catalog/{app-id}/items?region=apac_tier_1" \
  -H "Authorization: Bearer {app-id}|{app-token}" \
  -H "X-API-Version: 2.0.0"
When the app is opted into dynamic pricing, region is required and the response contains only items that belong to that region (with the regional suffix already stripped from each product_id). When the app is not opted in, region is ignored and the response contains all items in the default catalog.
A successful response returns 200 OK with a JSON array of catalog items:
[
  {
    "product_id": "coins_500",
    "product_title": "500 Gold Coins",
    "product_description": "A bundle of 500 gold coins to spend in the shop.",
    "product_type": "consumable",
    "price_amount_cents": 49,
    "price_currency_code": "USD",
    "region": "apac_tier_1"
  }
]
The region field is included on each item only when the app is opted into dynamic pricing. The product_id is always the identifier you defined (no regional suffix).

V2 error cases

In addition to the V1 error cases, the V2 endpoints can return:
Status CodeMeaningRecommended Action
400 Bad Request (region required)
Your app is opted into dynamic regional pricing and the region field/query parameter was omitted.
Always include region on V2 requests for opted-in apps.
400 Bad Request (invalid region)
The supplied region is not one of the supported enum values.
See the Region semantics table above for valid values.

SDK integration

The Instant Games SDK provides a complete set of APIs for handling in-app purchases. All payment APIs are in the FBInstant.payments namespace.

Checking IAP availability

Before showing any purchase UI, verify that the payments API is available on the current platform:
// Check if the payments API is supported on this platform
function isIAPAvailable() {
  const supportedAPIs = FBInstant.getSupportedAPIs();
  return supportedAPIs.includes('payments.getCatalogAsync');
}
This check is a best practice for defensive coding. Although IAP is supported on all platforms (iOS, Android, and web), there may be edge cases where payments are temporarily unavailable on a particular client or device. If payments APIs are not supported, hide your purchase buttons and rely on ads for monetization.

Initializing payments

Before making any payment calls, you must call onReady to signal that your game is prepared to handle payments:
FBInstant.payments.onReady(function () {
  console.log('Payments are ready.');
  // Now you can safely call getCatalogAsync, purchaseAsync, etc.
});
The onReady callback fires when the payments system has finished initializing. Do not call any other payment methods before this callback fires.

Getting the product catalog

Retrieve the list of products available for purchase:
FBInstant.payments.getCatalogAsync().then(function (catalog) {
  catalog.forEach(function (product) {
    console.log('Product:', product.productID);
    console.log('  Title:', product.title);
    console.log('  Description:', product.description);
    console.log('  Price:', product.price);
    console.log('  Price Currency Code:', product.priceCurrencyCode);
    console.log('  Image URI:', product.imageURI);
  });
}).catch(function (error) {
  console.error('Failed to get catalog:', error);
});
Each product in the catalog includes the following fields:
FieldTypeDescription
productID
string
The unique product identifier you created in the DFC.
title
string
The product name.
description
string
The product description.
price
string
The localized price string (e.g., “$4.99” or “3,99 EUR”).
priceCurrencyCode
string
The ISO 4217 currency code (e.g., “USD”, “EUR”).
priceAmount
string
The numeric price amount without currency symbol.
imageURI
string
The product image URL, if provided.

Making a purchase

To initiate a purchase, call purchaseAsync with a purchase configuration:
FBInstant.payments.purchaseAsync({
  productID: 'coins_500',
  developerPayload: 'optional_custom_data',
}).then(function (purchase) {
  console.log('Purchase successful!');
  console.log('  Product ID:', purchase.productID);
  console.log('  Purchase Token:', purchase.purchaseToken);
  console.log('  Signed Request:', purchase.signedRequest);

  // IMPORTANT: Verify the purchase server-side before delivering content.
  verifyPurchaseOnServer(purchase.signedRequest).then(function () {
    // Deliver the purchased content to the player
    deliverCoins(500);

    // If the product is consumable, consume it so it can be purchased again
    return FBInstant.payments.consumePurchaseAsync(purchase.purchaseToken);
  }).then(function () {
    console.log('Purchase consumed successfully.');
  });
}).catch(function (error) {
  // Handle errors (player cancelled, network error, etc.)
  console.error('Purchase failed:', error.code, error.message);
});
The developerPayload field is an optional string you can attach to the purchase. It is included in the signed receipt and can be used for your own tracking purposes (e.g., a session ID or order reference).

Getting unconsumed purchases

When your game starts, you should check for any unconsumed purchases from previous sessions. This handles the case where a player bought something but the game crashed before the content was delivered:
FBInstant.payments.getPurchasesAsync().then(function (purchases) {
  purchases.forEach(function (purchase) {
    console.log('Unconsumed purchase:', purchase.productID);

    // Verify and deliver the content, then consume the purchase
    verifyPurchaseOnServer(purchase.signedRequest).then(function () {
      deliverProduct(purchase.productID);
      return FBInstant.payments.consumePurchaseAsync(purchase.purchaseToken);
    }).then(function () {
      console.log('Recovered purchase consumed:', purchase.productID);
    });
  });
}).catch(function (error) {
  console.error('Failed to get purchases:', error);
});
Always call getPurchasesAsync on game start. This is critical for ensuring players receive what they paid for, even if something went wrong during a previous session.

Consuming a purchase

For consumable products, you must call consumePurchaseAsync after verifying and delivering the product:
FBInstant.payments.consumePurchaseAsync(purchaseToken).then(function () {
  console.log('Purchase consumed. Player can now buy this product again.');
}).catch(function (error) {
  console.error('Failed to consume purchase:', error);
});
Do not consume non-consumable (durable) products. Durable products should remain in the player’s getPurchasesAsync list permanently, indicating they own the item.

Complete purchase flow

Here is the recommended end-to-end purchase flow for a consumable product:
// 1. Check if IAP is available
if (!FBInstant.getSupportedAPIs().includes('payments.getCatalogAsync')) {
  console.log('IAP not available on this platform. Hiding store.');
  hideStoreUI();
  return;
}

// 2. Wait for payments to be ready
FBInstant.payments.onReady(function () {

  // 3. Recover any unconsumed purchases from previous sessions
  FBInstant.payments.getPurchasesAsync().then(function (purchases) {
    purchases.forEach(function (purchase) {
      recoverPurchase(purchase);
    });
  });

  // 4. Get the product catalog and display the store
  FBInstant.payments.getCatalogAsync().then(function (catalog) {
    displayStore(catalog);
  });
});

// 5. When the player taps a "Buy" button:
function onBuyButtonClicked(productID) {
  FBInstant.payments.purchaseAsync({
    productID: productID,
  }).then(function (purchase) {
    // 6. Verify the purchase on your server
    return verifyAndDeliver(purchase);
  }).then(function (purchase) {
    // 7. Consume the purchase (for consumable products)
    return FBInstant.payments.consumePurchaseAsync(purchase.purchaseToken);
  }).then(function () {
    // 8. Update the game UI
    updateStoreUI();
    showSuccessMessage('Purchase complete! Enjoy your items.');
  }).catch(function (error) {
    if (error.code === 'USER_INPUT') {
      // Player cancelled the purchase - this is normal, not an error
      console.log('Player cancelled the purchase.');
    } else {
      console.error('Purchase error:', error.code, error.message);
      showErrorMessage('Something went wrong. Please try again.');
    }
  });
}

Server-side verification

Never trust the client. Always verify purchases on your server before delivering content. Every purchase includes a signedRequest field that contains a cryptographically signed receipt you can validate.

How signedRequest works

The signedRequest is a string with two parts separated by a period (.):
<encoded_signature>.<encoded_payload>
  1. Decode the payload by Base64-decoding the second part (using URL-safe Base64: replace - with + and _ with / before decoding).
  2. Verify the signature by computing an HMAC-SHA256 hash of the payload using your App Secret, then comparing it to the decoded signature.
  3. Extract the purchase data from the decoded JSON payload.

Server-side verification example (Node.js)

const crypto = require('crypto');

function verifySignedRequest(signedRequest, appSecret) {
  const [encodedSignature, encodedPayload] = signedRequest.split('.');

  // Decode the signature
  const signature = Buffer.from(
    encodedSignature.replace(/-/g, '+').replace(/_/g, '/'),
    'base64'
  );

  // Compute the expected signature
  const expectedSignature = crypto
    .createHmac('sha256', appSecret)
    .update(encodedPayload)
    .digest();

  // Compare signatures securely
  if (!crypto.timingSafeEqual(signature, expectedSignature)) {
    throw new Error('Invalid signature');
  }

  // Decode and return the payload
  const payload = Buffer.from(
    encodedPayload.replace(/-/g, '+').replace(/_/g, '/'),
    'base64'
  ).toString('utf8');

  return JSON.parse(payload);
}

Verification payload fields

The decoded payload contains:
FieldDescription
product_id
The product that was purchased.
purchase_token
A unique identifier for this purchase.
purchase_time
Timestamp of the purchase.
developer_payload
The optional custom data you passed to purchaseAsync.
player_id
The Instant Games player ID.

Handling consumables vs. durables

The key difference in how you handle these two product types:

Consumable flow

  1. Player purchases the product.
  2. Verify the signedRequest on your server.
  3. Deliver the content (e.g., add 500 coins to the player’s balance).
  4. Call consumePurchaseAsync to mark the purchase as consumed.
  5. The product is now available for purchase again.

Durable flow

  1. Player purchases the product.
  2. Verify the signedRequest on your server.
  3. Deliver the content (e.g., unlock “Remove Ads” feature).
  4. Do not call consumePurchaseAsync.
  5. On future game starts, call getPurchasesAsync to check if the player owns this durable product, and apply the benefit accordingly.
// Example: Check for durable purchases on game start
FBInstant.payments.getPurchasesAsync().then(function (purchases) {
  const ownedProducts = purchases.map(function (p) { return p.productID; });

  if (ownedProducts.includes('remove_ads')) {
    disableAds();
  }

  if (ownedProducts.includes('premium_skin_dragon')) {
    unlockSkin('dragon');
  }
});

Error handling

IAP operations can fail for various reasons. Always handle errors gracefully:
Error CodeMeaningRecommended Action
USER_INPUT
The player cancelled the purchase dialog.
Do nothing. This is normal behavior, not an error.
NETWORK_FAILURE
A network error occurred during the purchase.
Show a friendly error message and suggest the player try again.
INVALID_PARAM
An invalid product ID or parameter was passed.
Check your product IDs match the catalog. This usually indicates a bug.
INVALID_OPERATION
The operation is not allowed (e.g., payments not ready).
Ensure onReady has fired and that payments are supported on the current client.
PAYMENTS_NOT_INITIALIZED
Payment APIs were called before onReady.
Wait for onReady before calling payment methods.
CLIENT_UNSUPPORTED
The current client does not support payments.
Hide purchase UI and use ads for monetization.
FBInstant.payments.purchaseAsync({ productID: 'coins_500' })
  .then(function (purchase) {
    handleSuccessfulPurchase(purchase);
  })
  .catch(function (error) {
    switch (error.code) {
      case 'USER_INPUT':
        // Player chose to cancel - not an error
        break;
      case 'NETWORK_FAILURE':
        showMessage('Network error. Please check your connection and try again.');
        break;
      case 'INVALID_OPERATION':
      case 'CLIENT_UNSUPPORTED':
        showMessage('Purchases are not available on this device.');
        hideStoreUI();
        break;
      default:
        showMessage('Something went wrong. Please try again later.');
        console.error('Unexpected purchase error:', error.code, error.message);
    }
  });

Testing IAP

You can test your entire IAP integration without spending real money using the In-App Test Payment System.

In-app test payment system

The In-App Test Payment System provides a mock payment dialog that lets developers and testers simulate purchases — including both success and failure scenarios — without processing real payments. Test purchases are not reported in revenue APIs or payout reports.
Requirements:
  • Instant Games SDK version 7.0 or later (7.0, 7.1, 8.0, or latest).
  • The user must have an app role (admin, developer, or tester) assigned in the App Dashboard.

Disabling test payments

Test payments are enabled by default for users with a role on the app. Only admins and developers of the game are able to disable test payments following these steps:
  1. In the App Dashboard, go to Game Payments > In-App Test Payment Settings.
  2. Toggle In-App Test Payments Mode to off. The IAP test mode applies only to your own account — other users and live players are not affected.
Game payments settings in DFC dashboard

Using the test dialog

The following screenshot shows the Mock IAP Test dialog:
In-App Test Payment dialog example with Success selected
When IAP test mode is enabled, initiating a purchase in your game displays the Mock IAP Test dialog instead of the real payment dialog. From this dialog you can:
  • Select Succeed to simulate a successful purchase. The SDK returns a mock Purchase object with test data.
  • Select an error code (for example, Invalid Operation) to simulate a specific failure scenario.
The test dialog behaves like the real purchase flow from the SDK’s perspective, so your purchaseAsync, consumePurchaseAsync, and getPurchasesAsync calls work normally.
Note: The Mock IAP Test dialog only simulates the initial part of the purchase flow, and does not add items into your getPurchasesAsync list — it generates a mock Purchase object and signedRequest for client-side testing. It does not complete the entire purchase flow, including server-side payment processing, webhook notifications, or actual financial transactions.
If you need to verify the real payment flow, click Switch to production payment flow in the test dialog to temporarily bypass test mode.

Test purchase tokens

Test purchases return a mock purchaseToken in the format 111111111<random_number>. The random suffix ensures each test purchase has a unique token, which is important if your server uses the purchase token as an idempotency key.

Adding testers

To let other people test IAP on your game without being app developers:
  1. In the App Dashboard, go to App Roles.
  2. Add the person as a Tester on your app. The tester can then see the mock IAP dialog.

Testing checklist

Before going live with IAP, verify the following:
  • All products appear correctly in your in-game store (correct names, descriptions, prices).
  • Purchasing a consumable product delivers the correct content and the product can be purchased again.
  • Purchasing a durable product delivers the correct content and persists across sessions.
  • Unconsumed purchases are recovered correctly when the game restarts (getPurchasesAsync).
  • Server-side verification succeeds and the decoded payload is correct.
  • Purchases complete successfully on iOS, Android, and web.
  • Error handling works correctly (cancel the purchase dialog, disconnect from the network, and so on).
  • The in-game store displays localized prices from the catalog (not hardcoded prices).
  • Both success and failure scenarios behave as expected using the In-App Test Payment System.

Revenue share and payout schedule

Facebook charges a revenue share on all In-App Purchase transactions. The standard revenue share for Instant Games is 70/30 — you receive 70% of the gross revenue and Facebook retains 30%. This is consistent with industry standard rates for digital storefronts.

Payout schedule

Payouts are processed on a monthly basis, typically within 21 days after the end of the calendar month in which the revenue was earned. For example, revenue earned in January would be paid out by approximately February 21.
Payouts are sent to the bank account you configured in your payout account setup. You can view your earnings and payout history in the App Dashboard under Monetization > Payout.

Taxes

Facebook provides tax documentation (1099 forms for US developers) but does not provide tax advice. You are responsible for reporting and paying taxes on your game revenue according to the laws of your jurisdiction. Consult a tax professional if you have questions.

Best practices for IAP design

Offer a strong first purchase

A player’s first purchase typically has the lowest conversion rate. To increase first-purchase conversion:
  • Offer a “Starter Pack” or “First Purchase Bonus” priced below the value of its contents. For example, a bundle worth $5 in virtual currency for $0.99.
  • Show this offer at a moment when the player clearly wants something (e.g., after running out of lives for the first time).
  • Present the offer for a limited time (“Available for the first 24 hours only”) so players have a reason to act.

Price strategically

  • Low entry points ($0.99 - $1.99) convert more players. Use these for consumables.
  • Mid-range bundles ($4.99 - $9.99) work well for currency packs and value bundles.
  • Premium purchases ($14.99 - $49.99) serve committed players. Make these feel like exceptional value compared to smaller bundles.
  • Always show the best value label on your most profitable bundle to guide players toward it.

Make purchases visible but not aggressive

  • Place your store in an easily accessible location (e.g., a “Shop” button on the main menu).
  • Use subtle prompts at natural moments (e.g., “Need more coins? Visit the shop!” after the player runs out).
  • Never block gameplay with mandatory purchase screens. Players who feel forced will leave.

Design consumables for repeat purchases

  • Currencies (coins, gems, diamonds) are the most effective consumable because they create a flexible economy and encourage repeat purchases.
  • Energy/lives systems create natural purchase moments when the player runs out.
  • Bundles that combine multiple items (“100 coins + 3 lives + 1 shield”) feel like better value and increase average order value.

Support non-paying players

  • Ensure that your game is enjoyable and completable without purchases. Players who feel pressured to pay will leave negative reviews and stop playing.
  • Use rewarded ads as a free alternative to purchases (e.g., watch an ad for 50 coins, or buy 500 coins for $0.99).
  • Non-paying players provide social value — they invite friends, compete on leaderboards, and fill your multiplayer sessions.

Next steps

  • IAP Payment Webhooks — Set up server-to-server webhook notifications for purchase and refund events.
  • In-App Ads — Learn how to generate revenue from ad impressions in your game.
  • Monetization Best Practices — Strategic guidance on combining IAP and ads for maximum revenue.
  • SDK Reference — Complete API documentation for all FBInstant.payments methods.