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
- Add the required Singular includes to your class header file.
- Implement a method to handle the OnSingularLinksResolved callback.
- Create and register the USingularDelegates object before SDK initialization.
-
Register your handler method using
OnSingularLinksResolved.AddDynamic(). - Process the deep link parameters in your handler method.
// 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:
- User clicks a Singular tracking link configured with a deep link value.
- User installs and opens the app for the first time.
- Singular SDK sends the first session to Singular servers.
- Attribution completes and identifies the deep link from the tracking link.
-
Deep link value returns to the handler with
IsDeferred = true.
Testing Deferred Deep Links:
- Uninstall the app from the test device (if currently installed).
- iOS: Reset your IDFA. Android: Reset your Google Advertising ID (GAID).
- Click the Singular tracking link from the device (ensure it's configured with a deep link value).
- 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:
- User clicks a Singular tracking link.
- The operating system provides an Open URL containing the entire Singular tracking link.
- During SDK initialization, Singular parses the URL.
-
Singular extracts the
deeplinkandpassthroughvalues. -
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.
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
// 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.
// 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.