Overview
AppStore.CachePurchasePayloadContext() was introduced in brainCloud 5.8. It tells brainCloud exactly which brainCloud product, price and promotion a user is about to buy, before the store purchase begins. When the receipt is later sent to VerifyPurchase(), brainCloud uses this cached context to award the correct product.
This call is optional and is not tied to StoreKit 2 or to any particular store SDK version. It doesn't change how purchases are made on the store side, and you can still process purchases the traditional way. What it adds is more flexibility and better control over how store purchases are mapped to in-game items.
Why you need it
The limitation: receipts only carry a product ID
Apple and Google receipts only return the store product ID (plus some basic transaction details). They don't say which in-game item, bundle or offer the user picked in your shop.
Apple and Google don't strictly force "one fixed price per product ID forever". You can change the price tier of a product in their consoles, and you don't always need a brand-new product ID to run a discount. The real problem is on the reward side. If you want different items or offers to share one product ID, the store alone can't tell you what to grant.
As a result, developers often encode both the item and the price into the product ID, which quickly multiplies the number of IAP IDs to manage:
apple_199
orange_199
banana_199
apple_299
orange_299
...
This makes store configuration harder to maintain. It also reduces flexibility when you want to reuse price points across different items, or change offers and promotions.
The solution: map product IDs to price points and cache the context
With CachePurchasePayloadContext(), you treat store products primarily as generic price points rather than individual items, for example a single fruit_199 product for everything priced at $1.99:
Store product ID → Price point
fruit_199 → $1.99
fruit_299 → $2.99
Just before starting the purchase, the client caches the extra context (the brainCloud payload) that says which item or bundle the user selected. When the receipt comes back with only fruit_199, brainCloud combines it with the cached payload to know exactly what to reward: apple, orange or banana.
This lets you:
Reuse product IDs across multiple items and promotions, and avoid an explosion of store product IDs.
Simplify store setup: create and approve fewer IAP products in App Store Connect, the Google Play Console and the Meta Horizon dashboard.
Support dynamic offers and promotions configured in brainCloud, without creating new store products.
Avoid wrong rewards when several brainCloud products share a store product ID.
Some stores used to let developers attach a "developer payload" to a purchase so this context could travel inside the receipt. Apple never supported this. Google Play has deprecated it, and Meta Horizon doesn't support it. CachePurchasePayloadContext keeps the context on the brainCloud side instead, so it works the same way across these stores.
How it works
The client calls GetSalesInventory(). Each product returned includes a
payloadstring, for example__bc:[1,apple_bundle,1,199,-1].When the user taps Buy, the client calls CachePurchasePayloadContext(storeId, iapId, payload) with that product's payload right before starting the native store purchase.
The client completes the purchase with the store (Google Play Billing, StoreKit or the Meta Horizon Platform SDK).
The client calls VerifyPurchase(storeId, receiptData) right away.
If the receipt doesn't carry a brainCloud payload, brainCloud looks up the cached context for the same app + user + storeId + iapId, created around the time of verification. It then uses that context to determine the product, price and promotion to award.
Parameters
Parameter | Description |
| The brainCloud store ID: |
| The store's own product ID. It must exactly match the ID in the receipt: Google Play |
| The |
The call returns an empty success response.
Payload format
The payload is generated by brainCloud and should be treated as opaque. For reference, its format is:
__bc:[versionId,itemId,priceId,referencePrice,promotionId]
__bc:[versionId,itemId,priceId,referencePrice,promotionId,campaignScenarioCode]
A promotionId of -1 means no promotion.
Timing matters
brainCloud matches a cached context to a receipt using the time of the VerifyPurchase call. By default, the context must have been cached no more than 60 seconds before VerifyPurchase is called (with a 5-second tolerance after). For this reason:
Call CachePurchasePayloadContext immediately before starting the store purchase, not when the shop screen opens.
Call VerifyPurchase as soon as the store returns a successful purchase.
Cached entries expire after 4 days by default.
Supported stores
Store | storeId | Uses cached context? | Notes |
Apple App Store |
| ✅ Yes | Apple receipts never contain a developer payload, so the cached context is always used. |
Google Play |
| ✅ Yes | If the receipt includes a brainCloud |
Meta Horizon (Quest) |
| ✅ Yes | Meta Horizon doesn't support developer payloads, so the cached context is the only way to pass the brainCloud context. Requires brainCloud 5.9 or later. |
Stores that don't use it
The following stores accept the call without error, but ignore the cached context during verification. Calling it for these stores has no effect:
Amazon (
amazon)Steam (
steam)Facebook (
facebook)Windows / Microsoft Store (
windows,windowsPhone)Epic Games (
epicGames)Xsolla (
xsolla): Xsolla carries the brainCloud payload in its own custom purchase data, so caching isn't needed.
These stores have different purchase flows (server-side or store-managed transactions), so you can keep using your existing VerifyPurchase or webhook integration for them.
The "Allow purchases without payload" compatibility setting
Go to App > Design > Core App Info > Advanced Settings and find "Allow purchases without requiring a developer payload or cached purchase context."
Enabled (the default for apps created before 5.8): caching is optional. If no context is found, brainCloud falls back to matching the product from the receipt's store product ID.
Disabled (the default for new apps): for
googlePlay,itunesandmetaHorizon, a purchase that has neither a developer payload nor a matching cached context fails verification. The error message is "Must use payload" and the result code is101.
If you reuse store product IDs across multiple brainCloud products, we recommend updating your Google Play, Apple and Meta Horizon purchase flows to cache the payload context, and then turning this setting off.
Errors
Reason code | Cause |
| The |
| The |
|
|
Examples
All examples assume you have already called GetSalesInventory() and have the selected product's store product ID and payload. They use the price-point approach above, where fruit_199 is shared by several items.
Raw API request (API Explorer)
Use service AppStore, operation CachePurchasePayloadContext:
{
"storeId": "googlePlay",
"iapId": "fruit_199",
"payload": "__bc:[1,apple_bundle,1,199,-1]"
}
Google Play (Unity)
// 1. Cache the brainCloud context right before purchasing
string storeId = "googlePlay";
string iapId = "fruit_199"; // Google Play productId
string payload = product["payload"].ToString(); // from GetSalesInventory
_bc.AppStoreService.CachePurchasePayloadContext(storeId, iapId, payload,
(response, cb) => {
// 2. Start the Google Play purchase
storeController.InitiatePurchase(iapId);
},
(status, code, error, cb) => Debug.LogError(error));
// 3. After Google Play reports success, verify immediately
var receipt = new Dictionary<string, object> {
{ "productId", iapId },
{ "orderId", purchase.orderId },
{ "token", purchase.purchaseToken }
};
_bc.AppStoreService.VerifyPurchase(storeId, JsonWriter.Serialize(receipt), OnVerifySuccess, OnVerifyFailure);
Apple App Store (Unity)
// 1. Cache the context; storeId for Apple is "itunes"
string storeId = "itunes";
string iapId = "fruit_199"; // App Store product_id
string payload = product["payload"].ToString();
_bc.AppStoreService.CachePurchasePayloadContext(storeId, iapId, payload,
(response, cb) => storeController.InitiatePurchase(iapId),
(status, code, error, cb) => Debug.LogError(error));
// 2. After StoreKit reports success, verify immediately.
// StoreKit 2 / App Store Server API: send the transaction ID
var receipt = new Dictionary<string, object> {
{ "transactionId", transactionId }
};
// Legacy receipts: { "receipt", base64AppReceipt }
_bc.AppStoreService.VerifyPurchase(storeId, JsonWriter.Serialize(receipt), OnVerifySuccess, OnVerifyFailure);
Meta Horizon / Quest (Unity)
// 1. Cache the context; iapId is the Meta Horizon SKU
string storeId = "metaHorizon";
string sku = "fruit_199";
string payload = product["payload"].ToString();
_bc.AppStoreService.CachePurchasePayloadContext(storeId, sku, payload,
(response, cb) => {
// 2. Launch the Meta Horizon checkout
IAP.LaunchCheckoutFlow(sku).OnComplete(OnCheckoutComplete);
},
(status, code, error, cb) => Debug.LogError(error));
// 3. On successful checkout, verify immediately
void OnCheckoutComplete(Message<Purchase> msg) {
if (msg.IsError) return;
var receipt = new Dictionary<string, object> {
{ "userId", metaUserId },
{ "sku", msg.Data.Sku },
{ "transactionId", msg.Data.ID }
};
_bc.AppStoreService.VerifyPurchase("metaHorizon", JsonWriter.Serialize(receipt), OnVerifySuccess, OnVerifyFailure);
}
Cloud Code
When purchases are routed through a client-session Cloud Code script:
var appStore = bridge.getAppStoreServiceProxy();
var result = appStore.cachePurchasePayloadContext(data.storeId, data.iapId, data.payload);
Note: this call isn't available from server-to-server (S2S) sessions, because the context is cached for the current user.
Best practices
Always use the
payloadfrom the latest GetSalesInventory() response. It reflects active promotions and price changes.Make sure
iapIdexactly matches the product ID that will appear in the receipt. A mismatch means the cached context won't be found.Cache once per purchase attempt. If the user cancels and buys again, cache again before the new attempt.
Keep the time between caching and VerifyPurchase short (60 seconds or less by default).
After every client build has been updated, disable "Allow purchases without requiring a developer payload or cached purchase context" to enforce correct product attribution.
