Unreal Engine SDK - Supporting Deep Links

Adding Deep Linking Support

Deep links direct users to specific content within your app. When users tap a deep link on a device with your app installed, the app opens directly to the intended content, such as a product page or specific experience.

Singular tracking links support both standard deep linking (for installed apps) and deferred deep linking (for new installs). For comprehensive information, see the Deep Linking FAQ and Singular Links FAQ.


Requirements

Prerequisites

Complete the Singular Links Prerequisites to enable deep linking for iOS and Android platforms.

Notes:

  • This guide assumes your organization is using Singular Links - Singular's tracking link technology launched in 2019. Older customers may be using legacy tracking links.
  • Your app's deep link destinations need to be configured on the Apps page in Singular (see Configuring Deep Link URLs).

SDK Configuration

Implement Singular Links Handler

The Singular SDK provides a delegate-based handler mechanism to retrieve deep link, deferred deep link, and passthrough parameters from Singular tracking links when your app opens.

Available Parameters:

  • Deeplink: The destination URL within your app for users clicking the link (configured in the Manage Links page).
  • Passthrough: Custom data passed through the tracking link for additional context or campaign metadata.
  • IsDeferred: Indicates whether the link is a deferred deep link (true for new installs, false for existing installs).

Add Deep Link Handler to Your App

Configure the deep link handler to process incoming deep link data when your app launches. This must be done before initializing the Singular SDK.

Implementation Steps

  1. Add the required Singular includes to your class header file.
  2. Implement a method to handle the OnSingularLinksResolved callback.
  3. Create and register the USingularDelegates object before SDK initialization.
  4. Register your handler method using OnSingularLinksResolved.AddDynamic().
  5. Process the deep link parameters in your handler method.
C++
// Add to the include section of your class header
#include "SingularLinkParams.h"
#include "SingularDelegates.h"

// In your class header, declare the handler method
UFUNCTION()
void OnSingularLinksResolved(const FSingularLinkParams& LinkParams);

// In your class implementation file, implement the handler method
void UYourGameInstance::OnSingularLinksResolved(const FSingularLinkParams& LinkParams)
{
    // Extract parameters from the tracking link
    const FString Deeplink = LinkParams.SingularLinksParams["deeplink"];
    const FString Passthrough = LinkParams.SingularLinksParams["passthrough"];
    const bool IsDeferred = LinkParams.SingularLinksParams["isDeferred"] == "true";

    // Log the parameters
    UE_LOG(LogTemp, Log, TEXT("Deep Link Resolved"));
    UE_LOG(LogTemp, Log, TEXT("Deeplink: %s"), *Deeplink);
    UE_LOG(LogTemp, Log, TEXT("Passthrough: %s"), *Passthrough);
    UE_LOG(LogTemp, Log, TEXT("Is Deferred: %s"), IsDeferred ? TEXT("true") : TEXT("false"));

    // Handle deep link routing
    if (!Deeplink.IsEmpty())
    {
        HandleDeepLinkNavigation(Deeplink, IsDeferred);
    }
}

void UYourGameInstance::HandleDeepLinkNavigation(const FString& Url, bool IsDeferred)
{
    // Your deep link routing logic
    UE_LOG(LogTemp, Log, TEXT("Routing to: %s (Deferred: %s)"), 
           *Url, IsDeferred ? TEXT("true") : TEXT("false"));

    // Example: Parse the URL and navigate to appropriate content
    if (Url.Contains(TEXT("product")))
    {
        NavigateToProduct(Url);
    }
    else if (Url.Contains(TEXT("promo")))
    {
        NavigateToPromotion(Url);
    }
    else
    {
        NavigateToHome();
    }
}

// Register the delegate BEFORE initializing the Singular SDK
void UYourGameInstance::InitializeSingular()
{
    // Create the delegates object
    USingularDelegates* SingularDelegates = 
        CreateDefaultSubobject<USingularDelegates>(TEXT("SingularLinksHandler"));

    // Register the deep link handler
    SingularDelegates->OnSingularLinksResolved.AddDynamic(
        this, 
        &UYourGameInstance::OnSingularLinksResolved
    );

    UE_LOG(LogTemp, Log, TEXT("Singular Link Handler registered"));

    // Now initialize the SDK
    bool Success = USingularSDKBPLibrary::Initialize(
        TEXT("YOUR_SDK_KEY"),
        TEXT("YOUR_SDK_SECRET"),
        60,    // Session timeout
        TEXT(""),    // Custom user ID
        true,  // Enable SKAdNetwork
        false, // Manual SKAN management
        0,     // Wait for tracking authorization
        false, // OAID collection
        true,  // Enable logging
        3,     // Log level
        false, // Clipboard attribution
        TEXT(""),    // Facebook App ID
        TEXT("")     // Custom SDID
    );

    if (Success)
    {
        UE_LOG(LogTemp, Log, TEXT("Singular SDK initialized successfully"));
    }
}

Important: The delegate must be registered before calling USingularSDKBPLibrary::Initialize(). If you register the handler after initialization, it will not receive deep link callbacks.


Handler Behavior

Understanding Deep Link Resolution

The deep link handler behaves differently depending on whether the app is freshly installed or already installed when the user taps a Singular Link.

Fresh Install (Deferred Deep Link)

On a fresh install, no Open URL exists when the app launches. Singular completes attribution to determine if the tracking link contained a deep link value.

Deferred Deep Link Flow:

  1. User clicks a Singular tracking link configured with a deep link value.
  2. User installs and opens the app for the first time.
  3. Singular SDK sends the first session to Singular servers.
  4. Attribution completes and identifies the deep link from the tracking link.
  5. Deep link value returns to the handler with IsDeferred = true.

Testing Deferred Deep Links:

  1. Uninstall the app from the test device (if currently installed).
  2. iOS: Reset your IDFA. Android: Reset your Google Advertising ID (GAID).
  3. Click the Singular tracking link from the device (ensure it's configured with a deep link value).
  4. Install and open the app.

Attribution should complete successfully, and the deferred deep link value will be passed to your handler.

Development Testing Tip: When testing deep links with a development build using a different package name or bundle ID, configure the tracking link specifically for the development app's identifier. After clicking the test link, install the development build directly onto the device via Unreal Engine or platform-specific tools, rather than downloading the production app from the app store.


Already Installed (Immediate Deep Link)

When the app is already installed, clicking a Singular Link opens the app immediately using Universal Links (iOS) or Android App Links technology.

Immediate Deep Link Flow:

  1. User clicks a Singular tracking link.
  2. The operating system provides an Open URL containing the entire Singular tracking link.
  3. During SDK initialization, Singular parses the URL.
  4. Singular extracts the deeplink and passthrough values.
  5. Values return through the handler with IsDeferred = false.

Passthrough Parameters

Capture additional data from the tracking link click using passthrough parameters for campaign metadata, user segmentation, or custom information.

If a passthrough (_p) parameter is included in the tracking link, the handler's Passthrough parameter contains the corresponding data.

C++
void UYourGameInstance::OnSingularLinksResolved(const FSingularLinkParams& LinkParams)
{
    // Extract passthrough data
    const FString PassthroughData = LinkParams.SingularLinksParams["passthrough"];

    if (!PassthroughData.IsEmpty())
    {
        // Parse JSON passthrough data
        TSharedPtr<FJsonObject> JsonObject;
        TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(PassthroughData);

        if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
        {
            // Extract campaign metadata
            FString CampaignId;
            if (JsonObject->TryGetStringField(TEXT("campaign_id"), CampaignId))
            {
                UE_LOG(LogTemp, Log, TEXT("Campaign ID: %s"), *CampaignId);
            }

            FString UserSegment;
            if (JsonObject->TryGetStringField(TEXT("segment"), UserSegment))
            {
                UE_LOG(LogTemp, Log, TEXT("User Segment: %s"), *UserSegment);
            }

            FString PromoCode;
            if (JsonObject->TryGetStringField(TEXT("promo_code"), PromoCode))
            {
                UE_LOG(LogTemp, Log, TEXT("Promo Code: %s"), *PromoCode);
                ApplyPromoCode(PromoCode);
            }

            // Use the data in your app
            ApplyCampaignSettings(CampaignId, UserSegment);
        }
        else
        {
            UE_LOG(LogTemp, Warning, TEXT("Failed to parse passthrough data"));
        }
    }
}

void UYourGameInstance::ApplyCampaignSettings(const FString& CampaignId, const FString& Segment)
{
    // Your campaign-specific logic
    UE_LOG(LogTemp, Log, TEXT("Applying campaign settings for: %s (Segment: %s)"), 
           *CampaignId, *Segment);
}

Passthrough Example URL:
https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&_p={"campaign_id":"summer2025","segment":"premium","promo_code":"SAVE20"}

The handler will receive the passthrough parameter containing the JSON string with campaign metadata.


FSingularLinkParams Reference

Parameter Details

The FSingularLinkParams object contains tracking link details passed to your deep link handler.

Parameter Type Description
deeplink FString The deep link destination URL, as configured in the Manage Links page in Singular. This value directs users to specific in-app content.
passthrough FString Passthrough parameters added to the tracking link, typically used for campaign metadata, user segmentation, or custom data that your app needs to process.
isDeferred bool Indicates whether the link is configured as a deferred deep link. Returns true for new installs (deferred), false for existing installs (immediate).

Method Signature

C++
// Callback method signature for Singular Links
void OnSingularLinksResolved(const FSingularLinkParams& linkParams)

// Access parameters using the SingularLinksParams map:
const FString Deeplink = linkParams.SingularLinksParams["deeplink"];
const FString Passthrough = linkParams.SingularLinksParams["passthrough"];
const bool IsDeferred = linkParams.SingularLinksParams["isDeferred"] == "true";

Deep Link Routing Implementation

Building a Deep Link Router

Implement a centralized deep link routing system to navigate users to specific content based on the deep link URL structure.

C++
// Deep Link Router class implementation
class UDeepLinkRouter : public UObject
{
public:
    void RouteDeepLink(const FString& DeepLinkUrl, bool IsDeferred)
    {
        UE_LOG(LogTemp, Log, TEXT("Routing deep link: %s (Deferred: %s)"), 
               *DeepLinkUrl, IsDeferred ? TEXT("true") : TEXT("false"));

        // Parse the URL scheme
        if (DeepLinkUrl.StartsWith(TEXT("myapp://product/")))
        {
            FString ProductId = ExtractParameter(DeepLinkUrl, TEXT("myapp://product/"));
            NavigateToProduct(ProductId);
        }
        else if (DeepLinkUrl.StartsWith(TEXT("myapp://category/")))
        {
            FString CategoryName = ExtractParameter(DeepLinkUrl, TEXT("myapp://category/"));
            NavigateToCategory(CategoryName);
        }
        else if (DeepLinkUrl.StartsWith(TEXT("myapp://promo")))
        {
            NavigateToPromotions();
        }
        else if (DeepLinkUrl.StartsWith(TEXT("myapp://profile")))
        {
            NavigateToUserProfile();
        }
        else
        {
            // Default fallback
            UE_LOG(LogTemp, Warning, TEXT("Unknown deep link format, navigating to home"));
            NavigateToHome();
        }

        // Track deep link usage
        TrackDeepLinkEvent(DeepLinkUrl, IsDeferred);
    }

private:
    FString ExtractParameter(const FString& Url, const FString& Prefix)
    {
        FString Param;
        if (Url.Split(Prefix, nullptr, &Param))
        {
            // Remove query parameters if present
            FString CleanParam;
            if (Param.Split(TEXT("?"), &CleanParam, nullptr))
            {
                return CleanParam;
            }
            return Param;
        }
        return FString();
    }

    void NavigateToProduct(const FString& ProductId)
    {
        UE_LOG(LogTemp, Log, TEXT("Navigating to product: %s"), *ProductId);
        // Your product screen navigation logic
    }

    void NavigateToCategory(const FString& CategoryName)
    {
        UE_LOG(LogTemp, Log, TEXT("Navigating to category: %s"), *CategoryName);
        // Your category screen navigation logic
    }

    void NavigateToPromotions()
    {
        UE_LOG(LogTemp, Log, TEXT("Navigating to promotions"));
        // Your promotions screen navigation logic
    }

    void NavigateToUserProfile()
    {
        UE_LOG(LogTemp, Log, TEXT("Navigating to user profile"));
        // Your profile screen navigation logic
    }

    void NavigateToHome()
    {
        UE_LOG(LogTemp, Log, TEXT("Navigating to home screen"));
        // Your home screen navigation logic
    }

    void TrackDeepLinkEvent(const FString& DeepLink, bool IsDeferred)
    {
        // Track deep link usage with Singular
        TMap<FString, FString> EventAttributes;
        EventAttributes.Add(TEXT("deep_link_url"), DeepLink);
        EventAttributes.Add(TEXT("is_deferred"), IsDeferred ? TEXT("true") : TEXT("false"));

        USingularSDKBPLibrary::EventWithAttributes(
            TEXT("deep_link_opened"), 
            EventAttributes
        );
    }
};

Best Practice: Track deep link opens as custom events in Singular to measure the effectiveness of your deep linking campaigns and understand which content drives the most engagement.


Important Considerations

Implementation Notes

  • Registration Timing: Always register the deep link handler before initializing the Singular SDK. The handler must be in place before the SDK processes the tracking link.
  • Thread Safety: The handler callback may execute on a background thread. Ensure any UI updates or navigation logic is dispatched to the game thread using Unreal Engine's thread-safe mechanisms.
  • URL Schemes: Design consistent URL schemes for your deep links (e.g., myapp://product/, myapp://category/) to simplify routing logic and maintenance.
  • Fallback Handling: Always implement fallback navigation (typically to the home screen) for unrecognized or malformed deep links to prevent app crashes or poor user experience.
  • Testing Both Flows: Test both immediate deep links (installed app) and deferred deep links (fresh install) to ensure proper handling in all scenarios.
  • Parameter Validation: Validate all parameters extracted from deep links before using them to prevent security issues or crashes from malicious or malformed URLs.
  • Platform Configuration: Complete platform-specific configuration (iOS Universal Links, Android App Links) as documented in the Singular Links Prerequisites guide.

Production Checklist: Before launching deep linking campaigns, verify: (1) Handler registered before SDK initialization, (2) Platform-specific configuration complete, (3) Deep links configured in Singular dashboard, (4) Routing logic handles all expected URL patterns, (5) Both immediate and deferred flows tested successfully.


Additional Resources