Unreal Engine SDK Setup Guide
This guide covers integrating Gaming Intelligence into Unreal Engine games using C++ and Blueprint.
Unreal Engine SDK Setup Guide
This guide covers integrating Gaming Intelligence into Unreal Engine games using C++ and Blueprint.
Prerequisites
Before you begin, make sure you have the following:
| Requirement | Details |
|---|---|
| SDK Key | Found in Console → Gaming → [Game Name] → Settings → SDK Configuration → SDK Key |
| Organization ID | Found in your Linkzly organization settings |
| Game ID | Found in Console → Gaming → [Game Name] → Settings |
| Unreal Engine | Version 5.0 or later |
| Project type | C++ project (not Blueprint-only) |
Step 1: Configure Build.cs
Open your game's Source/[GameName]/[GameName].Build.cs file and add "HTTP" and "Json" to PublicDependencyModuleNames:
// [GameName].Build.cs
public class MyGame : ModuleRules
{
public MyGame(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"InputCore",
"HTTP", // Required for FHttpModule
"Json", // Required for JSON serialization
"JsonUtilities"
});
}
}
Step 2: Create the Linkzly Gaming Subsystem
Create two files: LinkzlyGamingSubsystem.h and LinkzlyGamingSubsystem.cpp in your Source/[GameName]/ directory.
LinkzlyGamingSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "LinkzlyGamingSubsystem.generated.h"
USTRUCT(BlueprintType)
struct FLinkzlyEvent
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite)
FString EventId;
UPROPERTY(BlueprintReadWrite)
FString EventType;
UPROPERTY(BlueprintReadWrite)
FString Timestamp; // ISO 8601
UPROPERTY(BlueprintReadWrite)
FString PlayerId;
UPROPERTY(BlueprintReadWrite)
FString SessionId;
UPROPERTY(BlueprintReadWrite)
FString Platform = TEXT("unreal");
UPROPERTY(BlueprintReadWrite)
FString SdkVersion = TEXT("1.0.0");
UPROPERTY(BlueprintReadWrite)
FString GameVersion;
};
UCLASS()
class MYGAME_API ULinkzlyGamingSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// Called automatically by the engine when the GameInstance starts
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// Configuration — call this before sending any events
UFUNCTION(BlueprintCallable, Category = "Linkzly")
void Configure(const FString& InSDKKey, const FString& InOrganizationId, const FString& InGameId);
// Track a single event
UFUNCTION(BlueprintCallable, Category = "Linkzly")
void Track(const FLinkzlyEvent& Event);
// Track multiple events in a single batch
UFUNCTION(BlueprintCallable, Category = "Linkzly")
void TrackBatch(const TArray<FLinkzlyEvent>& Events);
// Convenience: start a session and set PlayerId/SessionId on all subsequent events
UFUNCTION(BlueprintCallable, Category = "Linkzly")
void Identify(const FString& InPlayerId, const FString& InSessionId);
private:
FString SDKKey;
FString OrganizationId;
FString GameId;
FString CurrentPlayerId;
FString CurrentSessionId;
void SendEvents(const TArray<FLinkzlyEvent>& Events);
FString BuildJsonPayload(const TArray<FLinkzlyEvent>& Events);
FString ComputeHmac(const FString& Timestamp, const FString& Nonce, const FString& Body);
FString GenerateUUID();
FString GetCurrentISOTimestamp();
void OnResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess);
};
LinkzlyGamingSubsystem.cpp
#include "LinkzlyGamingSubsystem.h"
#include "HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "Dom/JsonObject.h"
#include "Dom/JsonValue.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonWriter.h"
#include "Misc/Guid.h"
#include "Misc/DateTime.h"
// OpenSSL is bundled with Unreal Engine via ThirdParty/OpenSSL
#include "openssl/hmac.h"
#include "openssl/sha.h"
void ULinkzlyGamingSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogTemp, Log, TEXT("LinkzlyGamingSubsystem initialized"));
}
void ULinkzlyGamingSubsystem::Deinitialize()
{
Super::Deinitialize();
}
void ULinkzlyGamingSubsystem::Configure(
const FString& InSDKKey,
const FString& InOrganizationId,
const FString& InGameId)
{
SDKKey = InSDKKey;
OrganizationId = InOrganizationId;
GameId = InGameId;
}
void ULinkzlyGamingSubsystem::Identify(const FString& InPlayerId, const FString& InSessionId)
{
CurrentPlayerId = InPlayerId;
CurrentSessionId = InSessionId;
}
void ULinkzlyGamingSubsystem::Track(const FLinkzlyEvent& Event)
{
TArray<FLinkzlyEvent> Batch;
Batch.Add(Event);
SendEvents(Batch);
}
void ULinkzlyGamingSubsystem::TrackBatch(const TArray<FLinkzlyEvent>& Events)
{
SendEvents(Events);
}
void ULinkzlyGamingSubsystem::SendEvents(const TArray<FLinkzlyEvent>& Events)
{
if (SDKKey.IsEmpty() || OrganizationId.IsEmpty() || GameId.IsEmpty())
{
UE_LOG(LogTemp, Warning, TEXT("Linkzly: SDK not configured. Call Configure() first."));
return;
}
FString JsonPayload = BuildJsonPayload(Events);
// Generate HMAC signing headers
FString Timestamp = FString::FromInt(
(int64)(FDateTime::UtcNow() - FDateTime(1970, 1, 1)).GetTotalMilliseconds());
FString Nonce = GenerateUUID();
FString Signature = ComputeHmac(Timestamp, Nonce, JsonPayload);
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request =
FHttpModule::Get().CreateRequest();
Request->SetURL(TEXT("https://gaming.linkzly.com/api/v1/gaming/events"));
Request->SetVerb(TEXT("POST"));
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
Request->SetHeader(TEXT("Authorization"),
FString::Printf(TEXT("Bearer %s"), *SDKKey));
Request->SetHeader(TEXT("X-Organization-ID"), OrganizationId);
Request->SetHeader(TEXT("X-Game-ID"), GameId);
Request->SetHeader(TEXT("X-Timestamp"), Timestamp);
Request->SetHeader(TEXT("X-Nonce"), Nonce);
Request->SetHeader(TEXT("X-Signature-256"), Signature);
Request->SetContentAsString(JsonPayload);
Request->OnProcessRequestComplete().BindUObject(
this, &ULinkzlyGamingSubsystem::OnResponse);
Request->ProcessRequest();
}
FString ULinkzlyGamingSubsystem::BuildJsonPayload(const TArray<FLinkzlyEvent>& Events)
{
TArray<TSharedPtr<FJsonValue>> EventArray;
for (const FLinkzlyEvent& Evt : Events)
{
TSharedPtr<FJsonObject> EventObj = MakeShared<FJsonObject>();
EventObj->SetStringField(TEXT("event_id"), Evt.EventId.IsEmpty() ? GenerateUUID() : Evt.EventId);
EventObj->SetStringField(TEXT("event_type"), Evt.EventType);
EventObj->SetStringField(TEXT("timestamp"), Evt.Timestamp.IsEmpty() ? GetCurrentISOTimestamp() : Evt.Timestamp);
EventObj->SetStringField(TEXT("platform"), TEXT("unreal"));
EventObj->SetStringField(TEXT("player_id"), Evt.PlayerId.IsEmpty() ? CurrentPlayerId : Evt.PlayerId);
EventObj->SetStringField(TEXT("session_id"), Evt.SessionId.IsEmpty() ? CurrentSessionId : Evt.SessionId);
if (!Evt.SdkVersion.IsEmpty())
EventObj->SetStringField(TEXT("sdk_version"), Evt.SdkVersion);
if (!Evt.GameVersion.IsEmpty())
EventObj->SetStringField(TEXT("game_version"), Evt.GameVersion);
EventArray.Add(MakeShared<FJsonValueObject>(EventObj));
}
TSharedPtr<FJsonObject> RootObj = MakeShared<FJsonObject>();
RootObj->SetArrayField(TEXT("events"), EventArray);
FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(RootObj.ToSharedRef(), Writer);
return OutputString;
}
FString ULinkzlyGamingSubsystem::ComputeHmac(
const FString& Timestamp, const FString& Nonce, const FString& Body)
{
// Signing string format: "{timestamp}.{nonce}.{body}"
FString SigningString = FString::Printf(TEXT("%s.%s.%s"), *Timestamp, *Nonce, *Body);
std::string SigningStr(TCHAR_TO_UTF8(*SigningString));
std::string KeyStr(TCHAR_TO_UTF8(*SDKKey));
unsigned char Digest[EVP_MAX_MD_SIZE];
unsigned int DigestLen = 0;
HMAC(EVP_sha256(),
KeyStr.c_str(), (int)KeyStr.size(),
(const unsigned char*)SigningStr.c_str(), SigningStr.size(),
Digest, &DigestLen);
FString HexResult;
for (unsigned int i = 0; i < DigestLen; i++)
{
HexResult += FString::Printf(TEXT("%02x"), Digest[i]);
}
return HexResult;
}
FString ULinkzlyGamingSubsystem::GenerateUUID()
{
return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens).ToLower();
}
FString ULinkzlyGamingSubsystem::GetCurrentISOTimestamp()
{
FDateTime Now = FDateTime::UtcNow();
return Now.ToIso8601();
}
void ULinkzlyGamingSubsystem::OnResponse(
FHttpRequestPtr Request,
FHttpResponsePtr Response,
bool bSuccess)
{
if (!bSuccess || !Response.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("Linkzly: HTTP request failed."));
return;
}
int32 StatusCode = Response->GetResponseCode();
if (StatusCode == 202)
{
UE_LOG(LogTemp, Log, TEXT("Linkzly: Events accepted. Response: %s"),
*Response->GetContentAsString());
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Linkzly: Unexpected status %d. Body: %s"),
StatusCode, *Response->GetContentAsString());
}
}
Step 3: Track Events
From C++
Call Configure() once during game startup (e.g., in your AGameMode::BeginPlay() or UGameInstance::StartGameInstance()), then call Track() wherever events occur.
// In UMyGameInstance::StartGameInstance()
void UMyGameInstance::StartGameInstance()
{
Super::StartGameInstance();
ULinkzlyGamingSubsystem* Linkzly =
GetSubsystem<ULinkzlyGamingSubsystem>();
Linkzly->Configure(
TEXT("lnkz_sk_your_sdk_key_here"),
TEXT("org_your_org_id_here"),
TEXT("game_your_game_id_here")
);
// Set player identity after authentication
Linkzly->Identify(TEXT("player_123"), TEXT("session_abc"));
}
// Tracking an in-game event
void AMyPlayerController::OnItemPurchased(const FString& ItemId, float Amount)
{
ULinkzlyGamingSubsystem* Linkzly =
GetGameInstance()->GetSubsystem<ULinkzlyGamingSubsystem>();
FLinkzlyEvent Event;
Event.EventType = TEXT("purchase");
Event.GameVersion = TEXT("1.0.0");
// EventId and Timestamp are auto-filled if left empty
Linkzly->Track(Event);
}
From Blueprint
All public methods are exposed via BlueprintCallable. Add a Get Game Instance Subsystem node typed to Linkzly Gaming Subsystem, then call Configure, Identify, and Track directly in your Blueprint graph.
Step 4: Session Management
Tie sessions to UGameInstance lifecycle events so sessions start and end cleanly:
// UMyGameInstance.cpp
void UMyGameInstance::StartGameInstance()
{
Super::StartGameInstance();
ULinkzlyGamingSubsystem* Linkzly = GetSubsystem<ULinkzlyGamingSubsystem>();
Linkzly->Configure(TEXT("lnkz_sk_..."), TEXT("org_..."), TEXT("game_..."));
// Generate a fresh session ID each launch
FString SessionId = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens).ToLower();
Linkzly->Identify(TEXT(""), SessionId); // PlayerId set after login
}
void UMyGameInstance::Shutdown()
{
ULinkzlyGamingSubsystem* Linkzly = GetSubsystem<ULinkzlyGamingSubsystem>();
FLinkzlyEvent EndEvent;
EndEvent.EventType = TEXT("session_end");
Linkzly->Track(EndEvent);
Super::Shutdown();
}
Step 5: HMAC Signing
HMAC signing is enabled by default in the Linkzly console. The subsystem implementation above computes and attaches the required headers automatically.
If you need to disable HMAC during development: Console → Gaming → [Game Name] → Settings → Security Settings → HMAC Signing Required → Off.
Signing details:
| Header | Value |
|---|---|
X-Timestamp |
Unix timestamp in milliseconds |
X-Nonce |
UUID v4 |
X-Signature-256 |
HMAC-SHA256 hex of {timestamp}.{nonce}.{body} |
The replay protection window is 300 seconds. Requests with a timestamp older than 5 minutes are rejected.
Standard Event Types
| Event Type | Description |
|---|---|
session_start |
Player begins a game session |
session_end |
Player ends a game session |
level_start |
Player enters a level |
level_complete |
Player completes a level |
purchase |
In-game purchase |
ad_impression |
Ad shown to player |
custom |
Any custom event |
Troubleshooting
| Issue | Resolution |
|---|---|
| Events not appearing in dashboard | Verify SDK Key, Org ID, and Game ID are correct |
| 401 Unauthorized | SDK Key is invalid or revoked; regenerate in Console |
| 400 Bad Request | Check that event_type, player_id, and session_id are not empty |
| HMAC signature mismatch | Ensure system clock is synced (NTP); replay window is 300s |
| No HTTP module found | Confirm "HTTP" and "Json" are in PublicDependencyModuleNames |
| OpenSSL not found | UE bundles OpenSSL under Engine/Source/ThirdParty/OpenSSL; add it to your Build.cs if needed |
Next Steps
Was this helpful?
Help us improve our documentation