Integration
Quick Start
Section titled “Quick Start”using UnityEngine;using HyperstoneSdk;
public class MyGame : MonoBehaviour{ void Start() { // Subscribe to config changes — can be called before Initialize Hyperstone.WatchConfig(OnConfigChanged);
// Initialize with your credentials from the Hyperstone Console Hyperstone.Initialize("YOUR_APP_ID", "YOUR_APP_SECRET"); }
void OnConfigChanged(HyperstoneConfig config) { // Read optimized values sent by Hyperstone int lives = config.GetIntOrDefault("n_lives", 3); bool showAdsAfterDeath = config.GetBoolOrDefault("show_ads_after_death", true);
// Apply to your game logic GameManager.Instance.SetLives(lives); GameManager.Instance.SetAdsAfterDeath(showAdsAfterDeath); }
void OnDestroy() { Hyperstone.UnwatchConfig(OnConfigChanged); }}That’s it. Hyperstone will automatically:
- Fetch an optimized configuration tailored for this user
- Deliver it to your callback
- Track session metrics (sessions count, engagement time, retention)
- Send accumulated metrics to the Hyperstone backend
Initialization
Section titled “Initialization”Call Hyperstone.Initialize() once during your app startup (typically in a MonoBehaviour.Start() or a bootstrap script):
Hyperstone.Initialize("YOUR_APP_ID", "YOUR_APP_SECRET");| Parameter | Type | Description |
|---|---|---|
appId | string | Your application ID from the Hyperstone Console. |
appSecret | string | Your application secret from the Hyperstone Console. |
logLevel | HyperstoneClient.LogLevel | (Optional) Logging verbosity. Default: LogLevel.Info. |
You can find your App ID and App Secret in the Hyperstone Console.
Initialize must only be called once. Subsequent calls are ignored with a warning.
Configuration
Section titled “Configuration”Subscribing to Config Changes
Section titled “Subscribing to Config Changes”Use Hyperstone.WatchConfig() to receive configuration updates. You can subscribe at any point in your project — even before calling Initialize. This is particularly useful for decoupled architectures where different systems register their interest in config independently.
// Subscribe early — even before InitializeHyperstone.WatchConfig(OnConfigChanged);
void OnConfigChanged(HyperstoneConfig config){ // This fires: // 1. Immediately with the cached config (if one exists) // 2. Again when a fresh config arrives from the server}When you subscribe:
- If a config is already available (cached from a previous session or fetched from the server), your callback fires immediately with the current config.
- When a fresh config is fetched from the server, all registered callbacks fire again with the new config.
To unsubscribe:
Hyperstone.UnwatchConfig(OnConfigChanged);Reading Config Values
Section titled “Reading Config Values”HyperstoneConfig provides typed accessors with default value fallbacks. The config keys and their value types are defined by you in the Hyperstone Console when configuring your optimization parameters.
void OnConfigChanged(HyperstoneConfig config){ // Primitives with safe defaults int lives = config.GetIntOrDefault("n_lives", 3); float speed = config.GetFloatOrDefault("player_speed", 5.0f); double ratio = config.GetDoubleOrDefault("reward_ratio", 1.5); long bigNum = config.GetLongOrDefault("score_cap", 1000000L); bool showAds = config.GetBoolOrDefault("show_ads_after_death", true); string theme = config.GetStringOrDefault("ui_theme", "dark");
// TryGet pattern (returns false if key is missing or type mismatch) if (config.TryGetInt("bonus_level", out int bonus)) { // Use bonus }}For a full list of access methods, refer to the API Reference.
Deserializing Config as an Object
Section titled “Deserializing Config as an Object”If your config maps cleanly to a C# class, you can deserialize it in one call using Unity’s JsonUtility:
[Serializable]public class GameConfig{ public int n_lives; public bool show_ads_after_death; public float player_speed;}
void OnConfigChanged(HyperstoneConfig config){ GameConfig gameConfig = config.AsObject<GameConfig>(); // or overwrite an existing instance: // config.OverwriteObject(existingGameConfig);}Event Tracking
Section titled “Event Tracking”Hyperstone needs to know what happens in your app to optimize the configuration. The SDK provides methods for tracking standard events (ads, in-app purchases) and custom events/metrics.
Automatically Collected Data
Section titled “Automatically Collected Data”Some events and properties are tracked automatically by the SDK and don’t require any code.
User Device Properties
- SDK Version, Unity Version & Platform
- App bundle ID and numeric Version
- Operating system, Device model, and Device type (Phone/Tablet)
- Language code and Time zone
- Developer mode flag
System Metrics
- Session & Time — Total engagement time (in minutes) and total session counts. New sessions start on app launch or resuming from a timeout pause.
- Retention & Streaks — Total different days playing, continuous streaks, and calendar day tracking.
- Aggregated Revenue — Total overall revenue, total ad revenue/requests/impressions/errors, and IAP aggregate totals.
- Conversions — Purchaser conversion events (the first time a player makes a purchase).
Ad Events
Section titled “Ad Events”// User saw an ad — include the revenue for ILRDHyperstone.LogAdImpression( adPlatform: "admob", // Mediation platform (e.g., "admob", "ironsource", "max") adNetwork: "meta", // The actual ad network that served the ad adFormat: "rewarded", // Ad format ("banner", "interstitial", "rewarded", "native") revenueUsd: 0.0032 // Estimated revenue in USD);
// Ad was requestedHyperstone.LogAdRequest( adPlatform: "admob", adFormat: "interstitial");
// Ad failed to load/showHyperstone.LogAdError( adPlatform: "admob", adNetwork: "meta", adFormat: "interstitial", failReason: "no_fill" // Optional, defaults to "unknown");In-App Purchase Events
Section titled “In-App Purchase Events”You can measure in-app purchase revenue using the LogIapTransactionSuccess method. You must manually pass the localized price point to properly measure the revenue generated:
// Successful purchaseHyperstone.LogIapTransactionSuccess( productId: "gems_100", currency: "USD", price: 4.99 // The price will be tracked as IAP revenue for LTV calculation);
// Failed purchaseHyperstone.LogIapTransactionError( productId: "gems_100", failReason: "cancelled");The SDK automatically detects the first successful purchase and reports a purchaser conversion event. Subsequent ad revenue for purchaser users is additionally tracked as a separate measurement.
Custom Events and Metrics
Section titled “Custom Events and Metrics”For app-specific tracking, use custom events and metrics:
// Simple event (counted with value = 1)Hyperstone.LogCustomEvent("level_completed");
// Event with labels for segmentationHyperstone.LogCustomEvent("level_completed", new Label("difficulty", "hard"), new Label("world", "forest"));
// Numeric metric (accumulates the value)Hyperstone.IncrementCustomMetric("coins_spent", 50);
// Numeric metric with labelsHyperstone.IncrementCustomMetric("damage_dealt", 127.5f, new Label("weapon", "sword"), new Label("enemy_type", "boss"));Naming rules for custom metrics:
- Alphanumeric characters, hyphens, and underscores only (
[a-zA-Z0-9-_]) - Maximum 100 characters
Labels attach low-cardinality metadata to measurements. The Hyperstone service uses labels to segment and aggregate data. Good label candidates are things like ad networks, difficulty levels, or item categories — not unique user IDs or timestamps.
Ad Impression Revenue (ILRD) Examples
Section titled “Ad Impression Revenue (ILRD) Examples”Impression-Level Revenue Data (ILRD) is critical for optimizing ad revenue. Here’s how to integrate with popular ad mediation platforms.
using GoogleMobileAds.Api;
// When setting up your rewarded ad:rewardedAd.OnAdPaid += (AdValue adValue) =>{ // AdMob reports value in micros (1,000,000 micros = 1 unit of currency) double revenueUsd = adValue.Value / 1_000_000.0;
Hyperstone.LogAdImpression( adPlatform: "admob", adNetwork: "admob", // AdMob as network when not using mediation adFormat: "rewarded", revenueUsd: revenueUsd );};
// For mediated ads, extract the actual ad network from the adapter class name or response info:rewardedAd.OnAdPaid += (AdValue adValue) =>{ double revenueUsd = adValue.Value / 1_000_000.0;
var responseInfo = rewardedAd.GetResponseInfo(); var loadedAdapterName = responseInfo?.GetLoadedAdapterResponseInfo()?.AdSourceName ?? "unknown";
Hyperstone.LogAdImpression( adPlatform: "admob", adNetwork: loadedAdapterName, adFormat: "rewarded", revenueUsd: revenueUsd );};IronSource (LevelPlay)
Section titled “IronSource (LevelPlay)”using Unity.Services.LevelPlay;
// Subscribe to the impression data eventIronSourceEvents.onImpressionDataReadyEvent += OnImpressionDataReady;
void OnImpressionDataReady(IronSourceImpressionData impressionData){ if (impressionData.revenue.HasValue) { Hyperstone.LogAdImpression( adPlatform: "ironsource", adNetwork: impressionData.adNetwork ?? "unknown", adFormat: impressionData.adUnit ?? "unknown", revenueUsd: impressionData.revenue.Value ); }}Data Management & Utilities
Section titled “Data Management & Utilities”Clearing User Data
Section titled “Clearing User Data”To reset all locally stored Hyperstone data (e.g., when a user logs out or you want a clean slate):
Hyperstone.ClearData();This clears the stored user ID, cached config, accumulated metrics, session history, and purchaser status. The next Initialize call will treat this as a brand-new user.
Logging
Section titled “Logging”Control the SDK’s log verbosity:
Hyperstone.LogLevel = HyperstoneClient.LogLevel.Debug; // Verbose — useful during developmentHyperstone.LogLevel = HyperstoneClient.LogLevel.Info; // Default — initialization and key eventsHyperstone.LogLevel = HyperstoneClient.LogLevel.Warning; // Only warnings and errorsHyperstone.LogLevel = HyperstoneClient.LogLevel.Error; // Only errorsYou can also pass the log level directly at initialization:
Hyperstone.Initialize("YOUR_APP_ID", "YOUR_APP_SECRET", HyperstoneClient.LogLevel.Debug);All SDK logs are prefixed with [Hyperstone]: for easy filtering in the Unity Console.
How It Works
Section titled “How It Works”Understanding the SDK internals helps you use it effectively and debug integration issues.
Architecture
Section titled “Architecture”The Hyperstone static class is a facade over a singleton HyperstoneClient MonoBehaviour. On first access, the SDK creates a [Hyperstone] GameObject marked with DontDestroyOnLoad so it persists across scene loads.
sequenceDiagram
participant App as Your App
participant SDK as Hyperstone SDK
participant Storage as PlayerPrefs
participant Server as Hyperstone Server
Note over App, Server: Initialization & Config Fetch
App->>SDK: Hyperstone.WatchConfig(callback)
App->>SDK: Hyperstone.Initialize(appId, appSecret)
SDK->>Storage: Load cached UID, config, metrics
Storage-->>SDK: Cached data (if any)
SDK-->>App: callback(cached config)
SDK->>Server: POST /v1/c {user properties}
Note right of SDK: Headers: xp-app-id, xp-app-secret, xp-uid
Server-->>SDK: Config JSON + UID
SDK->>Storage: Persist UID + config
SDK-->>App: callback(fresh config)
Note over App, Server: Event Tracking
App->>SDK: LogAdImpression / LogIap / IncrementCustomMetric
SDK->>SDK: Accumulate in metrics buffer
SDK->>Storage: Persist buffer (crash-safe)
Note over SDK, Server: Metric Sending (debounced, with cooldown)
SDK->>Server: POST /v1/i {batched metrics}
Note right of SDK: Headers: xp-app-id, xp-app-secret, xp-uid
alt Success
Server-->>SDK: 200 OK
SDK->>Storage: Clear sent metrics
else Failure
Server-->>SDK: Error
SDK->>SDK: Restore metrics to buffer for retry
end
Note over SDK, Server: Automatic (no code required)
loop Every 15s while app is active
SDK->>SDK: Increment engagement time
end
SDK->>SDK: Track sessions, retention, streaks
Lifecycle
Section titled “Lifecycle”- Initialization —
Hyperstone.Initialize()creates/finds the singleton, stores your credentials, loads any cached config and metrics fromPlayerPrefs, and begins a config fetch request. - Config Fetch — The SDK sends a
POSTrequest to the Hyperstone API with device/user properties. The server responds with an optimized configuration and a unique user identifier (UID). Both are persisted locally. - Config Delivery — Once the config arrives, all registered
WatchConfigcallbacks are invoked. If a cached config existed from a previous session, callbacks fire immediately on subscribe with the cached version, and again when the fresh config arrives. - Metric Collection — Events (ad impressions, purchases, custom metrics, sessions, engagement time) are accumulated in an in-memory buffer and periodically persisted to
PlayerPrefs. This ensures no data is lost on crashes. - Metric Sending — The SDK batches metrics and sends them to the backend with a debounce window (0.5s) and a cooldown period (20s) to avoid overwhelming the server. If a send fails, metrics are restored to the buffer and retried on the next opportunity.
- Session Management — The SDK automatically tracks sessions, detecting new sessions on app launch and when the app resumes from a background pause longer than 30 seconds. Engagement time is measured in 15-second increments. Retention is tracked at both “soft day” granularity (16+ hours between sessions) and calendar-day granularity.
Local Persistence
Section titled “Local Persistence”All data is stored in Unity’s PlayerPrefs under keys namespaced by your App ID (e.g., xp-uid-{appId}, xp-config-{appId}). This means:
- Config and metrics survive app restarts
- Metrics that fail to send are buffered and retried
- Each app ID has isolated storage (relevant for multi-tenancy)
Thread Safety
Section titled “Thread Safety”All calls to the Hyperstone static API are thread-safe. You can safely call Hyperstone.Initialize(), Hyperstone.WatchConfig(), Hyperstone.LogAdImpression(), and all other methods from any thread — including background threads, async tasks, or job system callbacks.
Under the hood, the Hyperstone facade captures Unity’s main-thread SynchronizationContext at startup (via [RuntimeInitializeOnLoadMethod]). If a call is made from a non-main thread, it is automatically marshalled to the main thread using SynchronizationContext.Post(). This ensures that the underlying HyperstoneClient MonoBehaviour (which uses coroutines and Unity APIs) always runs on the main thread, while your calling code doesn’t need to worry about thread affinity.
Advanced: Multi-Tenancy
Section titled “Advanced: Multi-Tenancy”The Hyperstone static API manages a single shared client instance (singleton pattern). In most apps, this is all you need.
However, if your application needs to communicate with multiple Hyperstone apps simultaneously (e.g., a publishing platform hosting multiple titles, or a single app with separate optimization scopes), you can create and manage HyperstoneClient instances manually:
using UnityEngine;using HyperstoneSdk;
public class MultiTenantExample : MonoBehaviour{ private HyperstoneClient _gameplayClient; private HyperstoneClient _monetizationClient;
void Start() { // Create separate client instances for different app scopes _gameplayClient = CreateClient("GameplayOptimizer"); _gameplayClient.Initialize("GAMEPLAY_APP_ID", "GAMEPLAY_APP_SECRET"); _gameplayClient.WatchConfig(OnGameplayConfigChanged);
_monetizationClient = CreateClient("MonetizationOptimizer"); _monetizationClient.Initialize("MONETIZATION_APP_ID", "MONETIZATION_APP_SECRET"); _monetizationClient.WatchConfig(OnMonetizationConfigChanged); }
private HyperstoneClient CreateClient(string name) { var go = new GameObject($"[Hyperstone - {name}]"); DontDestroyOnLoad(go); return go.AddComponent<HyperstoneClient>(); }
void OnGameplayConfigChanged(HyperstoneConfig config) { int lives = config.GetIntOrDefault("n_lives", 3); // ... }
void OnMonetizationConfigChanged(HyperstoneConfig config) { int adCooldown = config.GetIntOrDefault("ad_cooldown_seconds", 60); // ... }}