Unreal Engine SDK
The official CROSS Pay plugin for Unreal Engine. It opens a CROSS Pay checkout session inside an in-app WebView, waits for the deep-link callback, and returns the result via Blueprint-callable delegates — no API keys on the client.
Compatibility
| Environment | Supported versions |
|---|---|
| Unreal Engine | 4.25+ · 5.x |
| Platforms | Android · iOS |
| Language | C++ (Blueprint-callable) |
| Plugin | CROSSxSdkUnrealPlugin |
Installation
The plugin is distributed via GitHub Releases and installed through the crossx-plugins.json manifest + Makefile system from the sample project. The plugins folder is .gitignore-listed — every team member runs make sdk-install after cloning or pulling.
1. Add crossx-plugins.json to your project root
{
"$schema": "https://crossnexus.com/schemas/crossx-plugins.schema.json",
"comment": "Edit the versions below, then run `make sdk-install`. Registry repo is public, so no GITHUB_TOKEN is required.",
"registry": {
"type": "github-releases",
"owner": "to-nexus",
"repo": "crossy-sdk-unreal-sample"
},
"plugins": {
"CROSSxSdkUnrealPlugin": "0.0.0-beta.25"
}
}
Update the version number to the latest release.
2. Copy the install scripts and Makefile
Copy the following files from the sample project into your project root:
scripts/install-plugins.sh(macOS / Linux)scripts/install-plugins.ps1(Windows PowerShell)Makefile
3. Run the installer
make sdk-install
This downloads CROSSxSdkUnrealPlugin-{version}.zip from GitHub Releases, validates the SHA-256 checksum, and extracts it into Plugins/. The dependency plugin CROSSxWebkitSdkUnrealPlugin is also installed automatically.
To update to a newer version, edit crossx-plugins.json and run make sdk-install again:
make sdk-update name=CROSSxSdkUnrealPlugin version=0.0.0-beta.26
4. Enable plugins in .uproject
{
"Plugins": [
{ "Name": "CROSSxSdkUnrealPlugin", "Enabled": true },
{ "Name": "CROSSxWebkitSdkUnrealPlugin", "Enabled": true }
]
}
5. Add module dependencies in Build.cs
PublicDependencyModuleNames.AddRange(new string[] {
"CROSSxSdkUnrealPlugin",
"CROSSxWebkitSdkUnrealPlugin"
});
6. Set your Project ID
In Config/DefaultGame.ini:
[/Script/CROSSxSdkUnrealPlugin.CROSSxSdkSettings]
ProdProjectId=YOUR_PROJECT_ID
Obtain your Project ID from the CROSSx console. This value is also read at build time to inject the deep-link URI schemes.
Prerequisites
In every example, CheckoutUrl / CheckoutId are values your game server obtains by calling POST /v1/payments and passes to the client.
- Store
MERCHANT_API_KEYonly in server environment variables or a secret manager. - The game client only ever receives
checkout_urlandcheckout_idfrom your server. - The deep-link callback result is not authoritative for order fulfillment. Confirm finalization via webhook or a server-side
GET /v1/payments/{id}lookup.
Payment flow
Your game server creates a payment session → the SDK opens a WebView and waits for the deep-link callback → your server finalizes the order via webhook. The deep-link callback result is not the source of truth for confirmation.
Quick start
#include "SDK/CROSSxSdkSubsystem.h"
// 1. Configure and initialize (call once, e.g. in GameInstance::Init)
UCROSSxSdkSubsystem* Sdk = GetGameInstance()->GetSubsystem<UCROSSxSdkSubsystem>();
FCROSSxSdkConfig Config;
Config.ProjectId = TEXT("YOUR_PROJECT_ID");
Config.AppId = TEXT("com.example.mygame");
Config.AppName = TEXT("My Game");
Config.Environment = ECROSSxSdkEnvironment::Development;
Sdk->Configure(Config);
Sdk->InitializeSdk(); // restores stored session if available
// 2. Your game server creates a checkout and returns these values
FString CheckoutUrl = ...; // from your server
FString CheckoutId = ...; // from your server
// 3. SDK opens WebView and waits for the deep-link callback
FCROSSxCrossPayPaymentDelegate OnComplete;
OnComplete.BindDynamic(this, &UMyWidget::HandleCrossPayResult);
Sdk->OpenCrossPayAndWaitResultAsync(CheckoutUrl, CheckoutId, OnComplete);
void UMyWidget::HandleCrossPayResult(const FCROSSxCrossPayPaymentResult& Result)
{
if (Result.bSuccess)
{
// Callback received — verify on your server before granting goods
UE_LOG(LogTemp, Log, TEXT("Callback status: %s"),
*UEnum::GetValueAsString(Result.Status));
MyGameServer->VerifyPayment(Result.CheckoutId);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("CrossPay error: %s"), *Result.ErrorMessage);
}
}
Configure deep link
The plugin injects the required URI schemes automatically at build time using Unreal Plugin Language (UPL) XML, reading ProdProjectId from Config/DefaultGame.ini. No manual manifest or Info.plist edits are required when ProdProjectId is set.
The plugin validates that ProdProjectId is non-empty at build time. An empty value will intentionally fail the build.
For reference, the plugin injects the following:
Android
Intent filter added to GameActivity in AndroidManifest.xml:
<activity android:name="com.epicgames.unreal.GameActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="webkit-YOUR_PROJECT_ID" />
</intent-filter>
</activity>
iOS
webkit-YOUR_PROJECT_ID added to Info.plist under URL Types.
SDK configuration
FCROSSxSdkConfig Config;
Config.ProjectId = TEXT("YOUR_PROJECT_ID");
Config.AppId = TEXT("com.example.mygame"); // required: gateway X-App-Id header
Config.AppName = TEXT("My Game");
Config.Environment = ECROSSxSdkEnvironment::Development; // resolves URLs automatically
Config.Theme = ECROSSxThemeMode::Dark;
Config.Locale = TEXT("en");
Config.bEnableDebugLogs = true;
Sdk->Configure(Config);
FCROSSxAuthResult Session = Sdk->InitializeSdk(); // restores stored session
ECROSSxSdkEnvironment::Development resolves API URLs automatically. Use Custom to set OAuthServerUrl, AuthApiUrl, and EmbeddedWalletGatewayUrl manually.
Trigger payment
Sdk->OpenCrossPayAndWaitResultAsync(
CheckoutUrl,
CheckoutId,
OnComplete,
300000 // TimeoutMs (default: 5 minutes)
);
The SDK:
- Appends a CSRF
statetoken and the callback endpoint toCheckoutUrl. - Opens the checkout page in the platform-native WebView.
- Waits for the payment callback to arrive — method varies by platform:
- Android · iOS: deep-link scheme
webkit-{ProjectId}://crosspay-callback - Windows · macOS: loopback HTTP
http://localhost:{port}/crosspay-callback
- Android · iOS: deep-link scheme
- Validates
stateand callsOnCompletewith the decoded result. - Calls
OnCompletewithbSuccess = falseand an error message on timeout.
Handle the result
FCROSSxCrossPayPaymentResult carries the decoded deep-link callback envelope.
| Field | Type | Description |
|---|---|---|
bSuccess | bool | true when callback was received without error |
Status | ECROSSxCrossPayPaymentStatus | PENDING · PAID · FAILED · EXPIRED · CANCELLED |
Provider | ECROSSxCrossPayPaymentProvider | BINANCE_PAY · PAYLETTER · CROSS_PAY |
CheckoutId | FString | Checkout ID echoed from the callback |
CheckoutUrl | FString | Original checkout URL |
Currency | FString | Payment currency |
Amount | FString | Payment amount |
ErrorMessage | FString | Error description when !bSuccess |
The deep-link callback result is advisory only. Always confirm the payment state with a server-side GET /v1/payments/{id} lookup or webhook before granting goods.
Error handling
Errors are surfaced through the result struct — Unreal does not use exceptions.
void UMyWidget::HandleCrossPayResult(const FCROSSxCrossPayPaymentResult& Result)
{
if (!Result.bSuccess)
{
// Timeout, WebView failure, or deep-link error.
// The user may have completed the payment without the callback reaching the app.
UE_LOG(LogTemp, Warning, TEXT("CrossPay result: %s"), *Result.ErrorMessage);
// Always verify on your server regardless of callback success/failure.
MyGameServer->VerifyPayment(Result.CheckoutId);
return;
}
// handle success
}
Idempotency
| Scenario | Behavior |
|---|---|
| Retry within the same session (network failure, WebView re-open, etc.) | Reuse the same CheckoutId — call OpenCrossPayAndWaitResultAsync again with the same CheckoutUrl and CheckoutId |
Payment reaches a terminal state (PAID / FAILED / EXPIRED / CANCELLED) | Discard — issue a new checkout by calling POST /v1/payments on your server |
| New payment with a different order / amount / player | Always a new checkout — reusing a previous CheckoutId may be rejected for amount mismatch |
CROSS Pay currently does not provide a refund feature. If the same order is charged twice, it cannot be reversed. Call POST /v1/payments with the same Idempotency-Key for retry scenarios.