Touchpoints & Curated Lists
Fetch and display reward widgets, product collections, and promotional content in your Unity game
Beyond the built-in store, you can render reward widgets and product collections directly in your game UI using touchpoints and curated lists.
Touchpoints 2.0
Requires PlaySuper Unity SDK 4.5.0 or later. TouchpointV2Manager does not exist in
earlier versions, so calling it will not compile rather than fail at runtime.
Upgrade with:
openupm add com.playsuper.unity@4.5.0Check what you are on in Packages/manifest.json, or in Window > Package Manager > PlaySuper.
Staying on an older SDK for now? Call GET /v2/touchpoints/serve directly — it is the same
endpoint this method wraps, and the response below is exactly what it returns. You lose the
typed model and OpenCta's attribution handling, nothing else.
TouchpointV2Manager and TouchpointManager are different systems, despite the similar
names. The TouchpointManager methods further down are the older node-based touchpoints: they
take a coinId and return a different shape. Upgrading to 4.5.0 changes nothing about them, and
nothing migrates on its own — an existing integration keeps working untouched until you port it
deliberately.
A touchpoint is an offer the studio places on one of their game's screens from the PlaySuper console. The game names the screen it is about to show; the server returns the artwork the studio chose and one concrete offer already composed into it — priced for this player, with the coin maths done. Your game draws two images and opens a URL.
Fetching a touchpoint
using PlaySuperUnity;
async void ShowHome()
{
var tp = await TouchpointV2Manager.Serve(TouchpointV2Manager.Screens.Home);
if (tp != null && tp.HasTouchpoint)
{
// tp.FrameImageUrl — the frame the studio picked
// tp.OfferImageUrl — the offer art, drawn in its centre
// tp.item.cta.label — the button copy
ShowTouchpoint(tp);
}
}"Nothing to show" is not an error. A non-null result with HasTouchpoint false means the
studio has not configured this screen, or has but no catalogue item is available right now.
Both are ordinary states and the call succeeded. Only null means the request itself failed.
Treat an unconfigured screen as normal and render without a touchpoint.
asset and item are always both present or both absent — a frame with an empty centre reads as
a broken build — so HasTouchpoint is a single check rather than two.
Opening the offer
TouchpointV2Manager.OpenCta(tp);Pass the response rather than building a URL. The server has already appended campaign attribution
to cta.url, and OpenStore reads it back off — a hand-built URL silently loses attribution for
every touchpoint your game opens.
Parameters
| Parameter | Type | Description |
|---|---|---|
screen | string | Which screen is about to be shown. See TouchpointV2Manager.Screens. Only home is live today; an unrecognised screen returns a 400 naming the valid ones. |
type | string | Optional. product, coupon or gift-card. Needed only when the screen has more than one placement — an ambiguous call is rejected rather than picked for you. |
utmSource / utmMedium / utmCampaign | string | Optional. Appended to the CTA URL. |
await TouchpointV2Manager.Serve(
TouchpointV2Manager.Screens.Home,
TouchpointV2Manager.ItemType.Product,
utmSource: "level_end");Response
public class TouchpointV2Response
{
public string screen;
public string placementId; // null when no placement is configured
public TouchpointV2Asset asset; // the frame
public TouchpointV2Item item; // the offer inside it
public string servedAt;
public bool HasTouchpoint { get; } // asset != null && item != null
public string FrameImageUrl { get; }
public string OfferImageUrl { get; }
public bool IsPortrait { get; }
}| Field | Notes |
|---|---|
asset.imageUrl | The frame you draw. Not assetUrl. |
asset.orientation | PORTRAIT or LANDSCAPE. |
item.imageUrl | The offer art, drawn in the frame's centre. |
item.pricing | currency, mrp, price, priceAfterCoins, discountPct. Null for coupons — prices are nested here, not on item itself. |
item.offerText | Pre-formatted coupon line, e.g. "Flat 40% off up to ₹200". Display as given. |
item.coins | coinsRequired and coinDiscount. Null when this game's coin cannot be spent on the item. |
item.cta | label and url. Hand the response to OpenCta rather than using the URL directly. |
Every figure is computed server-side for this player and this game's coin. The game does no pricing arithmetic.
Touchpoints
Touchpoints are visual widget configurations that control how rewards and promotions appear in your game. Use the TouchpointManager to fetch touchpoint data and render custom widgets.
This is the original node-based API, and it is still supported. New integrations should prefer Touchpoints 2.0 above, where the server composes the widget for you instead of returning a node tree to walk.
Fetching touchpoints
// Fetch by name (recommended for most use cases)
TouchpointResponse store = await TouchpointManager.GetTouchpointByName("flash-sale-card", "coin-uuid-abcd");
// Fetch by ID
TouchpointResponse offer = await TouchpointManager.GetTouchpointById(
"6f9a5737-0247-4a71-89eb-b5d02212f179",
"coin-uuid-abcd"
);
// List all active touchpoints
TouchpointListResponse list = await TouchpointManager.ListTouchpoints("coin-uuid-abcd");Processing touchpoint data
if (store?.nodes?.Length > 0)
{
TouchpointNode node = store.nodes[0];
Debug.Log($"Layout: {node.layoutHint}");
string bgImage = node.background?.images?[0];
string titleText = node.title?[0]?.text;
string titleColor = node.title?[0]?.color;
string ctaAction = node.cta?.action;
string ctaText = node.cta?.text;
bool timerEnabled = node.GetStyleHint<bool>("timerEnabled", false);
if (node.HasReward)
{
HydratedReward reward = node.GetReward();
string brandName = reward?.brandName;
}
}For the full touchpoint data model, example responses, and all available node fields, see the Touchpoints API Reference.
Curated lists
Curated lists are pre-configured collections of products or rewards set up by PlaySuper. Fetch them by name and display them in your game's UI.
Fetching a curated list
// Products
var products = await PlaySuperUnitySDK.Instance.GetCuratedList(
CuratedListType.Products,
"homepage_featured",
"your-coin-id"
);
// Rewards
var rewards = await PlaySuperUnitySDK.Instance.GetCuratedList(
CuratedListType.Rewards,
"daily_deals",
"your-coin-id"
);| Parameter | Type | Required | Description |
|---|---|---|---|
type | CuratedListType | Yes | CuratedListType.Products or .Rewards |
listName | string | Yes | Name of the curated list (provided by PlaySuper) |
coinId | string | Yes | Your game's coin ID |
version | string | No | API version for rewards (e.g., "2.0.0") |
Working with products
public async void LoadFeaturedProducts()
{
try
{
var response = await PlaySuperUnitySDK.Instance.GetCuratedList(
CuratedListType.Products,
"homepage_featured",
"your-coin-id"
);
foreach (var product in response.products)
{
Debug.Log($"Product: {product.name}");
Debug.Log($"Brand: {product.brandName}");
Debug.Log($"Image: {product.imageUrl}");
var sku = product.skus[0];
if (sku.playSuperPrice != null)
{
Debug.Log($"Price: {sku.playSuperPrice.discountedListingPrice}");
}
}
}
catch (Exception ex)
{
Debug.LogError($"Failed to load products: {ex.Message}");
}
}Key product fields:
| Field | Description |
|---|---|
name | Product display name |
brandName | Brand name |
imageUrl | Primary product image |
images | Additional product images |
description | HTML description |
plainTextDescription | Plain text description |
skus | Available variants with pricing |
optionTypes | Product options (size, color, etc.) |
ctaUrl | Deep link to view/purchase in store |
SKU pricing (sku.playSuperPrice):
| Field | Description |
|---|---|
listingPrice | Original price |
discountedListingPrice | Final price after discount |
discountPercent | Discount percentage |
Working with rewards
public async void LoadRewards()
{
try
{
var response = await PlaySuperUnitySDK.Instance.GetCuratedList(
CuratedListType.Rewards,
"gift_cards",
"your-coin-id"
);
foreach (var reward in response.rewards)
{
Debug.Log($"Title: {reward.metadata.campaignTitle}");
Debug.Log($"Brand: {reward.brandName}");
Debug.Log($"Image: {reward.metadata.campaignCoverImage}");
if (reward.price.Count > 0)
{
Debug.Log($"Price: {reward.price[0].amount} coins");
}
Debug.Log($"Available: {reward.inventory.availableQuantity}");
}
}
catch (Exception ex)
{
Debug.LogError($"Failed to load rewards: {ex.Message}");
}
}Key reward fields:
| Field | Description |
|---|---|
brandName | Brand name |
metadata.campaignTitle | Reward title |
metadata.campaignSubTitle | Reward subtitle |
metadata.campaignCoverImage | Cover image URL |
metadata.brandLogoImage | Brand logo URL |
metadata.termsAndConditions | Terms and conditions |
metadata.howToRedeem | Redemption instructions |
price[0].amount | Price in coins |
inventory.availableQuantity | Stock remaining |
ctaUrl | Deep link to store page |
Deep-linking to a product or reward
Both products and rewards include a ctaUrl that opens the store directly to that item:
PlaySuperUnitySDK.Instance.OpenStore(product.ctaUrl);
PlaySuperUnitySDK.Instance.OpenStore(reward.ctaUrl);
// With UTM tracking
PlaySuperUnitySDK.Instance.OpenStore(product.ctaUrl, "featured_banner");Contact your PlaySuper integration team to get curated list names and configure custom lists for your game.
Next, see the Complete Example for a full integration script, offline support, user properties, and troubleshooting.