Skip to main content

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

EnvironmentSupported versions
Unreal Engine4.25+ · 5.x
PlatformsAndroid · iOS
LanguageC++ (Blueprint-callable)
PluginCROSSxSdkUnrealPlugin

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_KEY only in server environment variables or a secret manager.
  • The game client only ever receives checkout_url and checkout_id from 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

One-line summary

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);
}
}

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.

Empty ProjectId causes a build error

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:

  1. Appends a CSRF state token and the callback endpoint to CheckoutUrl.
  2. Opens the checkout page in the platform-native WebView.
  3. 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
  4. Validates state and calls OnComplete with the decoded result.
  5. Calls OnComplete with bSuccess = false and an error message on timeout.

Handle the result

FCROSSxCrossPayPaymentResult carries the decoded deep-link callback envelope.

FieldTypeDescription
bSuccessbooltrue when callback was received without error
StatusECROSSxCrossPayPaymentStatusPENDING · PAID · FAILED · EXPIRED · CANCELLED
ProviderECROSSxCrossPayPaymentProviderBINANCE_PAY · PAYLETTER · CROSS_PAY
CheckoutIdFStringCheckout ID echoed from the callback
CheckoutUrlFStringOriginal checkout URL
CurrencyFStringPayment currency
AmountFStringPayment amount
ErrorMessageFStringError description when !bSuccess
Verify on your server

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

ScenarioBehavior
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 / playerAlways a new checkout — reusing a previous CheckoutId may be rejected for amount mismatch
No refunds

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.