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 your app.
Required Configuration Steps:
- Associated Domains: In Xcode, add a Singular Custom Subdomain to Signing & Capabilities > Associated Domains
- URL Types: Add the app scheme to your URL Types at Info > URL Types
- Singular Platform: Add your Apple Developer Team ID and Scheme in the Apps page in the Singular platform
Notes:
- If the app is already configured to use iOS Universal Links, the Universal Link domain already exists in Associated Domains and can remain. This domain should be added to the Supported Domains config option , as noted in the advanced section below
- You must also include the Singular custom link domain , so that Singular can track attributions from marketing campaigns and handle deep links from these campaigns
Implement Singular Links Handler
The SingularLinkHandler provides a callback mechanism to retrieve deep link, deferred deep link, and passthrough parameters from Singular tracking links when the app opens.
Available Parameters:
- Deep Link: The destination URL within your app for users clicking the link
- Deferred Deep Link: The destination URL for users who install the app after clicking the link
- Passthrough: Custom data passed through the tracking link for additional context
- URL Parameters: All query parameters from the tracking link URL
SDK Configuration
Add SingularLinkHandler to Config
Configure the SingularLinkHandler during SDK initialization to process incoming deep link and deferred deep link data.
func getConfig() -> SingularConfig? {
// Create config with API credentials
guard let config = SingularConfig(apiKey: "SDK_KEY", andSecret: "SDK_SECRET") else {
return nil
}
// Set deep link handler
config.singularLinksHandler = { params in
self.handleDeeplink(params: params)
}
return config
}
func handleDeeplink(params: SingularLinkParams?) {
// Get Deeplink data from Singular Link
let deeplink = params?.getDeepLink()
let passthrough = params?.getPassthrough()
let isDeferred = params?.isDeferred()
let urlParams = params?.getUrlParameters()
print("Deeplink: \(deeplink ?? "null")")
print("Passthrough: \(passthrough ?? "null")")
print("Is Deferred: \(isDeferred ?? false)")
// Handle deep link routing
if let url = deeplink {
handleDeepLinkRouting(url: url, isDeferred: isDeferred ?? false)
}
}
- (SingularConfig *)getConfig {
// Create config with API credentials
SingularConfig *config = [[SingularConfig alloc] initWithApiKey:@"SDK_KEY"
andSecret:@"SDK_SECRET"];
// Set deep link handler
config.singularLinksHandler = ^(SingularLinkParams *params) {
[self handleDeeplink:params];
};
return config;
}
- (void)handleDeeplink:(SingularLinkParams *)params {
// Get Deeplink data from Singular Link
NSString *deeplink = [params getDeepLink];
NSString *passthrough = [params getPassthrough];
BOOL isDeferred = [params isDeferred];
NSDictionary *urlParams = [params getUrlParameters];
NSLog(@"Deeplink: %@", deeplink ?: @"null");
NSLog(@"Passthrough: %@", passthrough ?: @"null");
NSLog(@"Is Deferred: %d", isDeferred);
// Handle deep link routing
if (deeplink) {
[self handleDeepLinkRouting:deeplink isDeferred:isDeferred];
}
}
Note: The SingularLinkHandler is triggered only when the app opens through a Singular Link. For more information, see the Singular Links FAQ .
Handler Behavior
Understanding SingularLinkHandler Execution
The SingularLinkHandler behaves differently depending on whether the app is freshly installed or already installed.
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 or deferred 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 SingularLinkHandler with
isDeferred = true
Testing Deferred Deep Links:
- Uninstall the app from the test device (if currently installed)
- Reset your IDFA on the device (Settings > Privacy > Apple Advertising > Reset Advertising Identifier)
- 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 the SingularLinkHandler.
Pro Tip:
When testing deep links with a development
build using a different bundle identifier (e.g.,
com.example.dev
instead of
com.example.prod
),
configure the tracking link specifically for the development app's
bundle identifier. After clicking the test link, install the development
build directly onto the device via Xcode or TestFlight, 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 iOS Universal Links technology.
Immediate Deep Link Flow:
- User clicks a Singular tracking link
- iOS provides an Open URL containing the entire Singular tracking link
- During SDK initialization, Singular parses the NSUserActivity or URL
- Singular extracts the deep link and passthrough values
-
Values return through the SingularLinkHandler with
isDeferred = false
Passthrough Parameters
Capture additional data from the tracking link click using passthrough parameters.
If a passthrough parameter is included in the tracking link, the SingularLinkHandler's
passthrough
parameter contains the corresponding data. Use
this for capturing campaign metadata, user segmentation data, or any
custom information you need in the app.
func handleDeeplink(params: SingularLinkParams?) {
// Extract passthrough data
if let passthroughData = params?.getPassthrough() {
// Parse JSON passthrough data
if let data = passthroughData.data(using: .utf8),
let jsonData = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
let campaignId = jsonData["campaign_id"] as? String
let userSegment = jsonData["segment"] as? String
print("Campaign ID: \(campaignId ?? "unknown")")
print("User Segment: \(userSegment ?? "unknown")")
}
}
}
- (void)handleDeeplink:(SingularLinkParams *)params {
// Extract passthrough data
NSString *passthroughData = [params getPassthrough];
if (passthroughData) {
// Parse JSON passthrough data
NSData *data = [passthroughData dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
if (!error && jsonData) {
NSString *campaignId = jsonData[@"campaign_id"];
NSString *userSegment = jsonData[@"segment"];
NSLog(@"Campaign ID: %@", campaignId ?: @"unknown");
NSLog(@"User Segment: %@", userSegment ?: @"unknown");
}
}
}
URL Parameters
Capture all query parameters from the tracking link URL using the
getUrlParameters()
method.
The SingularLinkHandler provides access to all URL parameters from the tracking link, giving you complete access to custom parameters and UTM tags.
func handleDeeplink(params: SingularLinkParams?) {
// Get all URL parameters
if let urlParams = params?.getUrlParameters() {
for (key, value) in urlParams {
print("Parameter: \(key) = \(value)")
}
// Access specific parameters
if let utmSource = urlParams["utm_source"] as? String {
print("UTM Source: \(utmSource)")
}
if let promoCode = urlParams["promo"] as? String {
print("Promo Code: \(promoCode)")
}
}
}
- (void)handleDeeplink:(SingularLinkParams *)params {
// Get all URL parameters
NSDictionary *urlParams = [params getUrlParameters];
if (urlParams) {
for (NSString *key in urlParams) {
NSLog(@"Parameter: %@ = %@", key, urlParams[key]);
}
// Access specific parameters
NSString *utmSource = urlParams[@"utm_source"];
if (utmSource) {
NSLog(@"UTM Source: %@", utmSource);
}
NSString *promoCode = urlParams[@"promo"];
if (promoCode) {
NSLog(@"Promo Code: %@", promoCode);
}
}
}
Example Tracking Link:
https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&utm_source=facebook&promo=SALE2024
The SingularLinkHandler will provide access to all parameters including
utm_source
and
promo
through the
getUrlParameters()
method.
Advanced Configuration
Handling Non-Singular Universal Links
Support Universal Links from third-party partners like Google Ads and Facebook for attribution tracking.
For Singular iOS SDK versions 12.0.3 and above, non-Singular Universal Links are supported by default.
Deprecated feature:
The
supportedDomains
property and the
startSession:...andSupportedDomains:
overloads are annotated
__attribute__((deprecated))
in the current SDK. The block below is shown only for legacy SDK versions
(below 12.0.3); upgrade the SDK rather than continuing to set
supportedDomains
in new code.
To support third-party deep links in older versions of the SDK:
- Add all associated domains (excluding sng.link ) to the supportedDomains configuration option in the Config object every time the Singular SDK is initialized
- This allows for third-party deep link behavior but does not allow for attribution to the deep link. For attribution, you still have to use Singular tracking links
- supportedDomains functionality assumes you have configured your app for Universal Links and currently host your own AASA file on your domain
func getConfig() -> SingularConfig? {
guard let config = SingularConfig(apiKey: "SDK_KEY", andSecret: "SDK_SECRET") else {
return nil
}
// Add supported domains for third-party Universal Links
config.supportedDomains = [
"subdomain.mywebsite.com",
"anothersubdomain.myotherwebsite.com"
]
return config
}
- (SingularConfig *)getConfig {
SingularConfig *config = [[SingularConfig alloc] initWithApiKey:@"SDK_KEY"
andSecret:@"SDK_SECRET"];
// Add supported domains for third-party Universal Links
config.supportedDomains = @[
@"subdomain.mywebsite.com",
@"anothersubdomain.myotherwebsite.com"
];
return config;
}
Supporting ESP Custom Tracking Link Domains
Enable Universal Links served by Email Service Providers (ESPs) for email campaign attribution.
The ESP domain must be HTTPS-enabled. Apple requires that iOS apps pull apple-app-site-association files from an HTTPS-enabled endpoint without redirects. Check with your ESP how they host this file for your app, as it may require DNS configuration on your site's DNS.
To support an ESP domain, add the custom tracking domain to the espDomains configuration option in the config object every time the Singular SDK is initialized.
func getConfig() -> SingularConfig? {
guard let config = SingularConfig(apiKey: "SDK_KEY", andSecret: "SDK_SECRET") else {
return nil
}
// Add ESP domains for email campaign tracking
config.espDomains = ["links.mywebsite.com"]
return config
}
- (SingularConfig *)getConfig {
SingularConfig *config = [[SingularConfig alloc] initWithApiKey:@"SDK_KEY"
andSecret:@"SDK_SECRET"];
// Add ESP domains for email campaign tracking
config.espDomains = @[@"links.mywebsite.com"];
return config;
}