Unity PC — Xsolla SDK 3 Integration Guide

Xsolla Purchase Flow Demo
This guide primarily benefits developers targeting off-platform PC distribution and Epic Games Store with dual billing. For testing and release, you'll want to set up a web shop and/or the Xsolla Game Launcher via your Publisher Account. For this guide though, you can follow along without them.
What We're Building
We're using the Unity 2D Game Kit as our demo project. The scenario is simple: a player dies, a popup appears offering two options. Restart and lose progress, or buy a full health restoration and respawn from the last checkpoint. That's a classic impulse-buy moment, and it's a perfect fit for Pay Station.
This guide covers the complete integration: Authenticate, Load Catalog, Purchase Product, and Finalize Purchase. Follow it end-to-end to build the flow yourself, or jump to the step you need. Either way, everything here maps directly to the SDK Explorer and Integration Widget, so you'll always know where to find more detail.
Here's what we're working with:
-
Unity version: 6.3.5
-
Demo project: Unity 2D Game Kit
-
Xsolla SDK
-
Authentication method: Device ID (no login required)
-
Payment UI: Xsolla Pay Station opening in the user's default browser (desktop default)
-
Purchase validation: Client-side only
Prerequisites
Before you get started, make sure you've taken care of the following:
-
Unity installed. This guide uses Unity 6.3.5, but any recent version with IAP support should work.
-
A Unity project to work with. If you don't have a project ready, you can follow along with the Unity 2D Game Kit, which is what we use throughout this guide.
-
Unity IAP package installed. The Xsolla SDK integrates with Unity's In-App Purchasing framework and requires the IAP package as a dependency.
-
An active Xsolla Publisher Account. We'll walk through everything you need to do inside your Publisher Account as part of this guide, but creating the account itself isn't covered. If you haven't done that yet, head to the Xsolla Publisher Account Sign Up Page and get that set up first.
Your Reference Resources
Whether you're following along with this article or going deeper afterwards, there are three places you'll want to know about.
The Xsolla SDK Documentation is your main reference. From here, you can find guides for creating your Publisher Account, other integration flows, and links to the tools below.
The SDK Explorer is a playable, interactive demo of the SDK. You can walk through the four steps of monetization by testing purchases in-game and seeing the logs output in real-time.
The Integration Widget is an interactive code reference available in the sidebar of the SDK documentation. It lets you step through each stage of the integration and see exactly what code gets added at each point. This is what we'll be copying from throughout this guide.

SDK Explorer - Integration Widget View (Large)
In summary, SDK Explorer is the playable demo, while Integration Widget serves as the code reference. Both cover the same four steps but address different needs.
The Four Stages of Integration
The Xsolla SDK organizes a complete payment integration into four distinct stages:
-
Authenticate: establishing who the player is. Before any transaction can happen, the SDK needs a valid user identity.
-
Load Catalog: fetching your products. The SDK pulls your item catalog from Xsolla's backend and registers those products with Unity's IAP layer, making them available for purchase at runtime.
-
Purchase Product: initiating the transaction. The moment the player hits buy, the SDK hands off to Pay Station, which handles the payment UI, processes the transaction, and returns a success or failure signal along with your purchase receipt.
-
Finalize Purchase: acting on the result. Your game responds to a completed transaction by validating it, granting the item, updating game state, and closing the transaction cleanly.
If you keep these four stages in mind, you'll always know where you are in the process and where to look in the SDK Explorer or Integration Widget for reference.
Step 0: Install the Xsolla SDK Package
The Integration Widget gives you the exact Git URL you need. Grab it from there, or directly from the GitHub repo:
https://github.com/xsolla/xsolla-sdk-unity.git
Then in Unity:
-
Go to Window > Package Manager
-
Click the + icon in the top left
-
Select Install package from Git URL
-
Paste the URL and click Install
You should now see the Xsolla SDK listed in your Package Manager.

Add SDK via Github URL

Xsolla SDK Installed via Package Manager
Step 1: Authenticate
Set Up the Script
Create a new empty GameObject in your scene hierarchy (we placed ours under a "System" label) and add a new C# script to it as a component. We're calling ours XsollaPayStation.

Create an Empty Object

Add XsollaPayStation.cs to your object
Head to the Integration Widget and select the Authenticate tab. This step provides the full class. Copy it and paste it over your script file, then update the class name to match your file name. Later steps will provide snippets to add to this class.
using System;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Extension;
using Xsolla.SDK.Store;
using Xsolla.SDK.Common;
using Xsolla.SDK.UnityPurchasing;
public class YourSDKIntegrationBehaviour : MonoBehaviour, IDetailedStoreListener
{
IStoreController _storeController;
IExtensionProvider _storeExtensions;
void Start()
{
var settings = XsollaStoreClientSettings.Builder.Create()
.SetProjectId(301871)
.SetLoginId("dfcb133b-6d0b-4937-b8d2-c4f4d58fb53a")
.Build();
var configuration = XsollaStoreClientConfiguration.Builder.Create()
.SetSettings(settings)
.SetSandbox(true)
.SetLogLevel(XsollaLogLevel.Debug)
.Build();
var module = XsollaPurchasingModule.Builder.Create()
.SetConfiguration(configuration)
.Build();
var configurationBuilder = ConfigurationBuilder.Instance(module)
// ... product registration (see "Load Catalog" step)
;
UnityPurchasing.Initialize(this, configurationBuilder);
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
_storeController = controller;
_storeExtensions = extensions;
}
public void OnInitializeFailed(InitializationFailureReason error)
{
Debug.LogError($"Initialization failed: {error}");
}
public void OnInitializeFailed(InitializationFailureReason error, string message)
{
Debug.LogError($"Initialization failed: {error}. Details: {message}");
}
// ... ProcessPurchase, OnPurchaseFailed (see "Purchase Product" and "Finalize Purchase" steps)
}

Step 1. Authentication
Expected Compilation Errors
The code from the Authenticate step does not satisfy the full Unity IAP interface contract. You'll see compile errors because
PurchaseProcessingResultandOnPurchaseFailedare not yet implemented. Add the following placeholder methods to resolve them. We'll add the proper implementation forOnPurchaseFailedin Step 3: Initiate a Purchase andPurchaseProcessingResultin Step 4: Finalize Purchase.
Placeholder methods to resolve compile errors
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
throw new NotImplementedException();
}
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
throw new NotImplementedException();
}
public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
{
throw new NotImplementedException();
}
This is a known friction point when using the Integration Widget step-by-step. The Widget builds up these methods across multiple steps, so some interface requirements won't be satisfied until later.
Configure Your Project ID and Login ID
Inside the copied code you'll see SetProjectId and SetLoginId calls with default placeholder values. These point to a generic Xsolla demo project. Fine for a quick sanity check, but you'll want to replace them with your own credentials to work with your own catalog. For that, you need to set up your project in your Publisher Account.
Creating a New Project in Publisher Account
- Log into your Publisher Account and click Create Project in the sidebar

Publisher Account: Create New Project
- Fill in the basics. For this guide:
-
Project Type: Game
-
Release Platforms: PC
-
Monetization: In-game transactions
-
Development Stage: Concept
-
Genre: Adventure
-
Game Engine: Unity

Completed Project Details
- Click Create Project and take note of your new Project ID

Setting Up a Login Project
A login project tells your SDK which authentication setup you want to use for your app. This can be configured by authentication type and platform. To create one:
- In your Publisher Account project, navigate to Players > Login in the sidebar

- Create a new Login Project
Standard Login Project
- Select Classic Login as the base method.

Configuring default method for login project as Classic Login
- Click Configure and navigate to Login API Integration

- Make sure Login with Device ID is enabled

Toggling on API Integration to enable Device ID authentication
Device ID is not the standard PC login flow
Device ID authentication is not the typical approach for PC, but it gets us up and running the fastest for this guide. For a production release, see authentication methods for all available options.
Save your settings and return to the login dashboard. You'll now see both your Project ID and your Login ID.

Head back to your editor and replace the placeholder values:
var settings = XsollaStoreClientSettings.Builder.Create()
.SetProjectId(YOUR_PROJECT_ID)
.SetLoginId("YOUR-LOGIN-UUID")
.Build();
Verify Authentication is Working
Run your game and check the Unity Console. You're looking for log entries confirming that the store initialized successfully and authentication completed.
You'll also see an error about no products being returned. That's completely expected at this stage since we have not loaded the catalog yet.

Error: No Products Available
Step 2: Load Catalog
Create Your Item in Publisher Account
- Head to your project in your Publisher Account and navigate to Item Catalog > All Items in the sidebar.

Navigating to the Catalog manager in the Publisher Account Dashboard
- Click Create Manually > Virtual Item.

- Fill in the details:
-
SKU: This maps to your item ID in-game.
-
Name: What players should see during the purchase flow.
-
Price: Set in real currency.
-
Leave the remaining options as defaults for now

Mark the item as Available!
Before saving, make sure you toggle the item to Available at the top right of the page. Without this, the item won't appear in your game even if the SKU is correct. This is the first thing to check if you later see a "no products found" error.
- Scroll down and click Create Item.
Connect the Catalog to Your Game
Return to the Integration Widget and select the Load Catalog step. There are two pieces of code to add.
public class YourSDKIntegrationBehaviour : MonoBehaviour, IDetailedStoreListener
{
// ... fields, methods, callbacks from previous steps
void Start()
{
// ... settings, configuration, module initialization (see "Authenticate" step)
var configurationBuilder = ConfigurationBuilder.Instance(module)
.AddProduct("ticket.10", ProductType.Consumable)
.AddProduct("ticket.20", ProductType.Consumable)
.AddProduct("ticket.30", ProductType.Consumable);
// ... UnityPurchasing.Initialize() invocation (see "Authenticate" step)
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
// ... callback initialization (see "Authenticate" step)
// Products available via _storeController.products.all
foreach (var product in _storeController.products.all)
{
string id = product.definition.id;
string title = product.metadata.localizedTitle;
string price = product.metadata.localizedPriceString;
string currency = product.metadata.isoCurrencyCode;
// Display product to user
}
}
}
First, in your Start function, set up the configuration builder to register your product with Unity's IAP layer:
Register your product SKU
var configurationBuilder = ConfigurationBuilder.Instance(module)
.AddProduct("full-health", ProductType.Consumable);
ProductType.Consumable tells Unity this item can be purchased more than once. Important for something like a health upgrade that players might buy repeatedly. If the Integration Widget includes multiple sample products, strip them down to just yours.
Second, the OnInitialized callback is where you capture references to the store controller and extensions. Storing these as class fields (_storeController and _storeExtensions) makes them available to the rest of your code for initiating purchases, looking up prices, and validating transactions in later steps.
Store references in OnInitialized
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
_storeController = controller;
_storeExtensions = extensions;
}
Retrieving product info from the controller
Once the controller is stored, you can iterate through the loaded catalog to pull each product's ID, title, price, and currency. Not required for the integration, but useful for verifying your catalog loaded correctly or for building store UI later.
Optional: log loaded products
foreach (var product in _storeController.products.all)
{
string id = product.definition.id;
string title = product.metadata.localizedTitle;
string price = product.metadata.localizedPriceString;
string currency = product.metadata.isoCurrencyCode;
Debug.Log($"Product: {id}, Title: {title}, Price: {price} {currency}");
}
Verify the Catalog is Loading
Run the game and check the Console. You should see your product listed with its details, and the "no products" error from Step 1 should now be gone.

Confirm products were requested and retrieved successfully
Step 3: Purchase Product
Add the Purchase Code
Head back to the Integration Widget and select the Purchase Product step. The key addition is the PurchaseSku() function:
public class YourSDKIntegrationBehaviour : MonoBehaviour, IDetailedStoreListener
{
// ... fields, methods, callbacks from previous steps
// Example purchase method
public void PurchaseSku()
{
_storeController.InitiatePurchase("ticket.10");
}
// Purchase result arrives in ProcessPurchase (see "Finalize Purchase" step)
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
if (failureReason == PurchaseFailureReason.UserCancelled)
{
// Handle user cancellation here
return;
}
Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureReason}");
}
public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
{
if (failureDescription.reason == PurchaseFailureReason.UserCancelled)
{
// Handle user cancellation here
return;
}
Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureDescription.reason}, message: {failureDescription.message}");
}
}
Purchase trigger function
For this example, we modified the original function to except a productID.
public void PurchaseSku(string productID)
{
_storeController.InitiatePurchase(productID);
}
The productID parameter means the same function works for any SKU in your catalog. When called, InitiatePurchase() launches Pay Station in the player's default browser to complete the checkout. There's no in-app web view. The player completes payment in their browser and returns to the game afterwards. Once the transaction finishes, Unity IAP fires ProcessPurchase() on success or OnPurchaseFailed() on failure.
Implement OnPurchaseFailed
The Purchase Product step also provides the failure handlers. Copy these from the Integration Widget to replace the NotImplementedException() placeholders from Step 1:
Replace both OnPurchaseFailed placeholders
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
if (failureReason == PurchaseFailureReason.UserCancelled)
{
// Handle user cancellation here
return;
}
Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureReason}");
}
public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
{
if (failureDescription.reason == PurchaseFailureReason.UserCancelled)
{
// Handle user cancellation here
return;
}
Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureDescription.reason}, message: {failureDescription.message}");
}
Both methods check for user cancellation first (the player closed Pay Station without completing the purchase) and log an error for any other failure reason. The second variant includes a more detailed failure description. Two versions exist because Unity IAP has two listener interfaces, each firing a slightly different signature. Having both means you're covered regardless of which interface is in play. These are complete as-is for this guide.
Wire Up Your Game Logic
The Integration Widget covers the purchase mechanics, but how you trigger PurchaseSku is up to you. Here's what we did for the 2D Game Kit demo project:
Singleton pattern.
We made the class a singleton so it's accessible from any script in the project. Quick to set up, though you might prefer a different pattern for your final release.
public class XsollaPayStation : MonoBehaviour, IDetailedStoreListener
{
public static XsollaPayStation Instance { get; private set; }
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
Live pricing helper
We added a GetProductPrice function that pulls the current price for a given SKU from the store controller. Updating the price in your Publisher Account means the in-game price updates the next time the catalog loads. Keep in mind: if the catalog isn't reloaded during a session, players will still see the previous price.
public string GetProductPrice(string productId)
{
if (_storeController == null) return "...";
var product = _storeController.products.WithID(productId);
return product != null ? product.metadata.localizedPriceString : "...";
}
UI Implementation
In a separate UI class, we wired the purchase button to call XsollaPayStation.Instance.PurchaseSku("full-health") on click, and used a co-routine to update the price text via GetProductPrice().

Buy Health Button Implementation
Test Your Purchase Flow
Run the game and trigger your purchase. Your default browser should open with Pay Station showing your product name, price, and payment options.
Since we're in sandbox mode, you'll have access to test cards directly within Pay Station. Use these rather than your own card details. You can find the full list of test card details in the Xsolla developer resources.
Complete the purchase and check your Unity Console for the purchase confirmation in the logs.

Browser Checkout
At this point, nothing has happened in-game yet. The purchase was processed, but the game doesn't know what to do with it. That's the final step.
Step 4: Finalize Purchase
Understanding Client-Side vs Server-Side Validation
There are two validation approaches available.
Server-side validation uses webhooks or the Xsolla Events API. Unity IAP is told the purchase is pending and your code polls an event queue waiting for external confirmation. When that confirmation arrives, ConfirmPendingPurchase fires and consumes the transaction.
Client-side validation, which is what we're using, skips all of that. We confirm the purchase directly within the client the moment the transaction succeeds. No waiting, no polling, no pending state.
Why straight to Purchase Processing Complete?
Our ProcessPurchase returns PurchaseProcessingResult.Complete immediately, rather than Pending. This is intentional. The Pending + ConfirmPendingPurchase pattern provides no additional benefit in a client-side only setup. There is no server or Events API maintaining a persistent transaction queue. Returning Complete is the correct approach when you're validating and fulfilling entirely on the client.
Server-side validation for production
Client-side validation is a good starting point and appropriate for many games. For high-value economies or competitive games where fraudulent item grants would have meaningful impact, consider migrating to server-side validation using Xsolla's webhook system . Server-side validation is harder to intercept or tamper with, as the receipt never passes through client code you don't control.
Implement Finalize Purchase
Head to the Integration Widget, select Finalize Purchase, and copy the PurchaseProcessingResult() function.
public class YourSDKIntegrationBehaviour : MonoBehaviour, IDetailedStoreListener
{
// ... fields, methods, callbacks from previous steps
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
var product = args.purchasedProduct;
var validator = _storeExtensions.GetExtension().GetValidator();
validator.Validate(product.receipt, (success, error) =>
{
if (error == null && success)
{
// Purchase is valid — grant the item to the player, then confirm:
_storeController.ConfirmPendingPurchase(product);
}
else
{
// Purchase validation failed — do not award item
Debug.LogWarning($"Validation failed for {product.definition.id}");
}
});
return PurchaseProcessingResult.Pending;
}
}
Here's what our final implementation looks like after adapting it for Client-side Validation:
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
var product = args.purchasedProduct;
var validator = _storeExtensions
.GetExtension()
.GetValidator();
validator.Validate(product.receipt, (success, error) =>
{
if (success)
{
Debug.Log($"Purchase successful: {product.definition.id}");
// Your game logic goes here: grant the item, update state, etc.
}
else
{
Debug.LogWarning($"Validation failed for {product.definition.id}");
}
});
return PurchaseProcessingResult.Complete;
}
Compared to the Integration Widget default, we've stripped out the server-side validation code and are using only the client-side path. We return Complete immediately and handle the result inside the Validate callback.
Add Your Game Logic
The success branch inside the Validate callback is where your game responds to a confirmed purchase. The key point is that this is where it lives.
For our 2D Game Kit demo project, we:
-
Destroyed the purchase UI popup
-
Triggered the player respawn function with full health from the last checkpoint
Example game logic in success branch
if (success)
{
Debug.Log($"Purchase successful: {product.definition.id}");
// Destroy the purchase popup
if (purchaseUI != null)
{
Destroy(purchaseUI);
}
// Trigger respawn with full health
PlayerController.Instance.RespawnWithFullHealth();
}
Verify the Complete Flow
Run the game end-to-end. This is the full test:
-
Player dies. Popup appears with the purchase option showing the live price
-
Click the purchase button. Default browser opens with Pay Station
-
Complete the purchase using a test card
-
Return to Unity. Player respawns with full health, UI popup is dismissed

Xsolla Purchase Flow Demo Check the Console for the full finalization sequence: validation firing, the purchase confirmation log, and the transaction being closed.
What You've Built
That's a complete Xsolla payment integration in Unity for PC. Device ID authentication, a dynamic catalog, Pay Station via external browser, and client-side purchase validation.
The final class structure you should have:
-
Initialization and authentication configured with your Project ID and Login ID
-
Catalog registration with your product SKU(s) declared to Unity IAP
-
PurchaseSkuwired up to your game UI trigger -
ProcessPurchasereturningCompleteimmediately and triggering your game's post-purchase logic -
OnPurchaseFailed(both variants) present and handling failure gracefully
Remember to disable sandbox before release
If you've been testing with SetSandbox(true), make sure to set it to false before shipping. Leaving sandbox enabled in production means real purchases won't process.
Where to Go Next
Next steps from here:
-
Add more products.
-
Extend
AddProductand your store UI to support a full catalog -
Move to server-side validation or Events API.
-
For a production release, the Xsolla Events API or webhook-based server validation is the recommended upgrade from the client-side approach used in this guide. The SDK Explorer makes it easy to see exactly what changes
-
You may need to contact your account manager or email integrations@xsolla.com to get the Events API configured for your project
-
Explore other authentication methods.
-
Device ID is quick to set up but doesn't persist across device changes. Xsolla supports email, social, and platform-native authentication
-
Publisher Account gives you control over payment methods, UI theming, and regional settings
-
Set up a Web Shop and/or the Xsolla Game Launcher.
-
For off-platform PC distribution and release, these are some of your options
The Xsolla SDK site has detailed walkthroughs, quick guides, and videos for every feature covered here and beyond. The SDK Explorer is worth revisiting once you have a working integration.
Good luck, and if you run into anything, the Xsolla developer community and support team are there to help.