Skip to main content

Deep Link

Deep links for Android devices and universal links for iOS devices are methods to open specific content in your app.

A deep link is a URI that redirects visitors to content in an app, while web links are deep links that use the HTTP or HTTPS scheme. In Android, deep links can be created by adding an intent-filter to an activity in your app. As other apps (such as the system browser) installed on a player's device may also be able to handle the same intent, the player can choose to open your deep link with a different app.

For Android 6.0 (API level 23) and later versions, Android App Links can be used to set your app as the default handler of a certain link type, by adding the autoVerify attribute to web links. The attribute lets the Android system verify whether your app corresponds to the host domain specified in the intent-filter. However, players can still choose to change the default handler by manually configuring the settings on their device.

For more information, see Handling Android App Links.

Step 1: Configure Player Network SDK

Version requirements

Player Network SDK V1.12.00 or later

  1. In AndroidManifest.xml, create an intent-filter for your URI.

    Custom scheme link

    Configure a deep link:

    • scheme - Custom scheme
    • host - Custom host domain
    • pathPrefix (optional) - Custom path prefix, see Multi-store packages

    Code sample for deep link lipass://app/xxxxxx:

    <activity
    android:name=".ExampleActivity"
    android:exported="true"
    ...>
    <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="lipass"
    android:host="app" />
    </intent-filter>
    </activity>
    HTTP/HTTPS scheme link

    Choose to configure either a web link (deep link with the HTTP/HTTPS scheme) or an Android App Link (web link with automatic verification for default app handling):

    • scheme - http or https
    • host - Custom host domain
    • pathPrefix (optional) - Custom path prefix, see Multi-store packages

    Code sample for web link https://www.levelinfinite.com/app/xxxxxx:

    <activity
    android:name=".ExampleActivity"
    android:exported="true"
    ...>
    <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="http"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    <data android:scheme="https"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    </intent-filter>
    </activity>

    Code sample for Android App Link https://www.levelinfinite.com/app/xxxxxx (supported in Android 6.0 and above):

    <activity
    android:name=".ExampleActivity"
    android:exported="true"
    ...>
    <intent-filter android:autoVerify="true"><!-- enable automatic verification -->
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="http"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    <data android:scheme="https"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    </intent-filter>
    </activity>

    Code sample:

    <activity
    android:name=".ExampleActivity"
    android:exported="true"
    ...>
    <intent-filter>
    <!-- This intent-filter supports the `lipass://app/xxxxxx` deep link -->
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="lipass"
    android:host="app" />
    </intent-filter>
    <intent-filter android:autoVerify="true">
    <!-- This intent-filter supports the `https://www.levelinfinite.com/app/xxxxxx` Android App Link -->
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="http"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    <data android:scheme="https"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    </intent-filter>
    </activity>
  2. Receive intent data.

    Call the getData() and getAction() methods in the onCreate and onNewIntent of the MainActivity to retrieve the data and actions associated with the incoming Intent.

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Intent intent = getIntent();
    String action = intent.getAction();
    Uri data = intent.getData();

    if (data != null) {
    INTLSDK.Deeplink.receive(data.toString());
    }
    }

    public void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    INTLSDK.onNewIntent(intent);

    Uri appLinkData = intent.getData();
    if (appLinkData != null) {
    INTLSDK.Deeplink.receive(appLinkData.toString());
    }
    }

Step 2: Configure website

note

This step is only required if Android App Links have been configured, and is optional if your app does not use Android App Links.

To verify that you own both your app and the web domain through Google Search Console, deploy the Digital Asset Links JSON file at the following location:

https://domain.name/.well-known/assetlinks.json

The Digital Asset Links JSON file uses the following fields to identify the associated app:

  • package_name: The app ID declared in the build.gradle file.

  • sha256_cert_fingerprints: The SHA256 fingerprints of your app's signing certificate. This field supports multiple fingerprints, which can support different versions of your app, such as debugging and production builds.

    Use the following command to generate the fingerprints via the Java keytool:

    $ keytool -list -v -keystore my-release-key.keystore

The sample assetlinks.json below grants permissions for android_app to open com.example links:

[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]

[Optional] Multi-store packages

Image: Multi-store configuration

For channel packages with different package names, the android:pathPrefix attribute can be used to distinguish them. For example, if android:pathPrefix="/sample" is added to channel A, if both channel A and channel B packages are installed, the http://sdk-deeplink-confs.intlgame.com/sample link will pull up channel A.

Example of JSON file configurations for multi-store packages:

[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.puppies.app",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.monkeys.app",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8: 8A:04:96:B2:3F:CF:44:E5"]
}
}]
info

Due to the limitations with its built-in browser, players cannot be redirected to another app by opening a link in WeChat.

After installing the app, open the test link from the Android text editor app:

  1. When a link is clicked in an Android text editor app (such as Notepad), the log will record [ (intl_deeplink_manager.cpp:37) Receive].

    Image: app_link

  2. The general syntax for using adb to test an intent-filter URL is:

    $ adb shell am start
    -W -a android.intent.action.VIEW
    -d "URL" PACKAGE

    For example, the following command attempts to view the target app activity for lipass://app/xxxxxx.

    $ adb shell am start
    -W -a android.intent.action.VIEW
    -d "lipass://app/xxxxxx" com.example.android_app
note

When a web URI is invoked, the Android system will attempt each operation in the following order until the request is successful:

  1. If the default handler for the link type has been specified, open the app.
  2. Open the only app that can handle the link type.
  3. Allow the player to select the app to be opened from the disambiguation dialog.

Universal link is a mechanism for redirection between web and app interfaces introduced on Apple devices with iOS 9 and above. With universal links, users can open an app link in Safari or WebView to automatically open the relevant app while keeping all parameter data. Universal link solves several problems with the URL Scheme:

  1. When redirect fails, links are opened in Safari or WebView.
  2. Universal links provides unified web and native routing.
  3. Redirects do not open popup prompts, improving the user experience.
note

Player Network SDK for iOS platform only handles deep links in universal links. For the OpenUrl method where AppDelegate is opened through a custom scheme, Player Network SDK will not handle the method nor return the deep link callback, the game will have to handle this on their own.

Step 1: Configure web server

Configure JSON file apple-app-site-association and add it in the root directory of your web server. Universal links do not require deployment in the official website domain. Just ensure the deployment location is in a high-level directory that can be accessed. If deploying it on the official website, refer to the configuration example below or see Supporting associated domains.

The required configuration for deploying universal links on the official website:

{
"applinks": {
"apps": [],
"details": [
{
"appID": "9JA89QQLNQ.com.apple.wwdc",
"paths": [ "/wwdc/news/", "/videos/wwdc/2015/*"]
},
{
"appID": "ABCD1234.com.apple.wwdc",
"paths": [ "*" ]
}
]
}
}
  1. The apps key must be an empty array
  2. The details key corresponds to a dictionary. Website supports one dictionary per app, The order of the dictionaries in the array determines the order the system follows when looking for a match. Therefore, it is able to designate an app to handle specific parts of the website.
  3. Each dictionary contains one appID key and one paths key:
    • appID is the team ID or app ID prefix followed by the bundle ID.
    • paths is a string array to specify the website paths supported by the app and the website paths not associated with the app. When specifying the section not to be associated with the universal link, add "NOT" (including the space)to the start of the path string, for example, "paths": [ "/wwdc/news/", "NOT/videos/wwdc/2010/*", "/videos/wwdc/201?/*"]. As the system paths evaluates each path in the array in the specified order, and stops evaluating when a positive or negative match is found, specify high-priority paths before low-priority paths.
info

For re-signed apps, the appID and paths keys of the re-signed certificate must also be configured here.

To specify website paths(paths)in apple-app-site-association:

  • Use * to indicate the entire site.
  • Use a specified URL to indicate a specified link, such as /wwdc/news/.
  • Use * to indicate any string, and add * to specified URL to indicate a section of the website, such as /videos/wwdc/2015/*.
  • Use ? to match any character, and combine both wildcard characters (* and ?) in one URL, such as /foo/*/bar/201?/mypage.
info

Strings specifying website paths in the paths array must be case-sensitive.

Step 2: Configure associated domains

  1. To enable the Associated Domains capability, contact your capability manager to apply for the relevant permissions. For more information, see Supporting associated domains.

    Image: Associated Domains

  2. After enabling the Associated Domains capability, generate a new signed configuration profile.

  3. Configure Xcode capabilities. From Signing & Capabilities in the Xcode project, enable Associated Domains and add your Domains according to the format applinks:your_link.

    Image: Associated_Domains_Xcode

    note

    The Domains configuration does not require https://.

Games can also add and enable Associated Domains automatically by using the below code.

Add the following code in PostProcess:

#if UNITY_2019_3_OR_NEWER
var capManager = new UnityEditor.iOS.Xcode.ProjectCapabilityManager(projPath, entitlementsFilePath, targetGuid: targetProjectName);
#else
var capManager = new UnityEditor.iOS.Xcode.ProjectCapabilityManager(projPath, entitlementsFilePath, targetProjectName);
#endif

capManager.AddAssociatedDomains(new string[] { "your_link1", "your_link2", "your_link3" });

Step 3: Configure Player Network SDK

Version requirements

Player Network SDK V1.12.00 or later

As the INTLCore is not currently effective for the hook of this entry method, add the iOS lifecycle entry function in the UnityAppController class of Unity's UnityAppController.mm file.

The XCodePostProcess.cs code has automatically added the application:continueUserActivity:restorationHandler method in the compiled process flow (users do not need to add additional code in the function).

note

Do not add the lifecycle entry function in a UnityAppController sub-class and ensure that the function is in the same class as application:openURL:options: and other system lifecycle functions.

- (BOOL)application:(UIApplication *)application continueUserActivity:
(NSUserActivity *)userActivity restorationHandler:(void(^)
(NSArray<id<UIUserActivityRestoring>> *
__nullablerestorableObjects))restorationHandler {
return YES;
}

Step 4: Test your universal links

info

Due to the limitations with its built-in browser, players cannot be redirected to another app by opening a link in WeChat.

When the URL associated with your app is clicked in the iOS Notes app, a redirect to your app will be triggered and the following log will be recorded:

INTLApplicationDelegate:continueUserActivity:restorationHandler

Sample logs:
Image: INTL_Universal_Link_Log

note

When an app is opened for the first time after an installation or update, the system will request the apple-app-site-association file from the corresponding domain. If the universal link is not accessible but the configuration is correct, try restarting the device or reinstalling the app.

After apple-app-site-association and the Xcode project of the app is set up, the entire universal link operation process is controlled by the system. According to prediction, the following scenarios will occur the first time the app is opened after an installation or update:

  1. App initiates a GET request to get the apple-app-site-association file from the domain configured in the project.
  2. App registers apple-app-site-association with the system.
  3. When an URL is opened in WebView, it will be checked against the registered apple-app-site-association URLs.
  4. If there is a match, system opens the app and triggers the universal link delegate.
  5. If there is no match, system continues to open the URL in WebView.

AppsFlyer supports the following deep linking functions provided by AppsFlyer SDK:

  • Deep linking: Deep linking happens when the target app is already installed. The configured deep link sends users to specific in-app locations or to complete specific in-app activities.

  • Deferred deep linking: Deferred deep linking happens when the target app is not installed. The configured deep link sends users to the download page of the target app in a specific app store. When the user installs and opens the app, deferred deep linking sends the user to the target in-app location or completes target in-app activities.

A feature unique to AppsFlyer, AppsFlyer OneLink refers to attribution links that function across platforms, supports forwarding a user to the target app from various source platforms (such as email, social media, web, QR code, etc.), in the process also relaying the ID of the user. The operations team is required to confirm if the ID returned is the OpenID or the device ID,then use the resulting user IDs in a final data analysis to attribute the relevant channels to the invited users.

Step 1: Create an app on the AppsFlyer console

Create your app according to the AppsFlyer documentation Add App.

Step 2: Configure OneLink

note

If you are new to OneLink Management, follow the user guide for new users to create a OneLink template. For more information, see OneLink templates

  1. Go to AppsFlyer OneLink Management.

  2. On the top-right corner, click the menu icon then select New OneLink Template, and record the template ID.

    Image: OneLink Configuration

  3. On the OneLink setup page, enter the template name, subdomain, and select the app that was created previously.
    Record the subdomain you entered.

    Image: OneLink Configuration 2

  4. For Android apps, in the When app is installed section, click Use App Links to launch the app to add SHA-256 into keystore. For more information, see Android initial setup.

    Image: OneLink Configuration 3

  5. Record down the intent-filter code generated by the AppsFlyer console.

    Image: OneLink Configuration 4

  6. Save the OneLink template.

Step 3: Configure Player Network SDK

Android
  1. Add the following code to INTLConfig.ini under the [AppsFlyer] node and replace {OneLink_Template_ID} with the template ID recorded in Step 2.

    APPSFLYER_APP_INVITE_ONELINK_ID_ANDROID = {OneLink_Template_ID}
  2. If [Android LifeCycle] in INTLConfig.ini does not contain AppsFlyer, add AppsFlyer.

    LIFECYCLE = WeChat,QQ,Twitter,Adjust,Facebook,Google,Line,VK,Garena,Discord,Dmm,Update,Firebase,KaKao,WhatsApp,Permission,GooglePGS,AppsFlyer
  3. In AndroidManifest.xml, add the Android intent-filter code recorded from Step 2 under the MainActivity node of the game.
    This code will be used to launch the game, replace {Subdomain} and {OneLink_Template_ID} accordingly.

    <activity
    android:name="com.intlgame.unity.MainActivity"
    android:configChanges="fontScale|keyboard|keyboardHidden|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
    android:exported="true"
    android:label="@string/app_name"
    android:launchMode="singleTask">
    <intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data
    android:host="{Subdomain}.onelink.me"
    android:pathPrefix="/{OneLink_Template_ID}"
    android:scheme="https" />
    </intent-filter>
    </activity>
iOS
  1. Add the following code to INTLConfig.ini under the [AppsFlyer] node and replace {OneLink_Template_ID} with the template ID recorded in Step 2.

    APPSFLYER_APP_INVITE_ONELINK_ID_IOS = {OneLink_Template_ID}
  2. In Assets\INTLSDK\Editor\XUPorter\XCodePostProcess.cs, add the subdomain (for example appslinks:subdomain.onelink.me) obtained from Step 2 to the below line.

    capManager.AddAssociatedDomains(new string[] { "appslinks:subdomain.onelink.me"});

For more information, see OneLink links and experiences.

Step 4: Call the OneLink API

  1. Set the callback for generating OneLink.

    INTLAPI.AddExtendResultObserver(OnExtendEvent);// Setting callbacks
    INTLExtend.Invoke("AppsFlyer", "generateInviteLinkUrl", paramsJsonString);// paramsJsonString is the information to be relayed to OneLink
    public void OnExtendEvent(INTLExtendResult extendResult)
    {
    if ("generateInviteLinkUrl".Equals(extendResult.ExtendMethodName))// Methods to generate OneLink
    {
    if (extendResult.RetCode == 0)// OneLink generation success
    {
    string inviteUrl = extendResult.RetMsg;// The generated OneLink, which can be shared with other players
    //TODO:share link to other players
    }
    else// Failed to generate OneLink
    {
    //TODO: handle OneLink Failed result
    }
    }
    }

  2. Set the result callback after the invited user clicks on the generated OneLink.

    Generating OneLink and clicking OneLink events share a single callback, distinguished by the parameter INTLExtendResult in the callback.

      INTLAPI.AddExtendResultObserver(OnExtendEvent);// Setting callbacks
    public void OnExtendEvent(INTLExtendResult extendResult)
    {
    if ("OnOneLinkResult".Equals(extendResult.ExtendMethodName))// OneLink click callback
    {
    if (extendResult.RetCode == 0)// OneLink click callback success
    {
    string jsonParameter = extendResult.ExtraJson;// Parameters returned by AF after clicking on OneLink
    //TODO:Handle OneLink Parameter
    }
    else// OneLink Click callback failed
    {
    //TODO: handle OneLink Failed result
    }
    }
    }

  3. Refer to the following code to generate OneLink, and replace the parameters within the code accordingly.

    StringBuilder sb = new StringBuilder();
    sb.Append("{");
    sb.Append("\"deep_link_value\":\"abc\"").Append(","); //<TARGET_VIEW>

    sb.Append("\"deep_link_sub1\":\"1234\"").Append(",");//<PROMO_CODE>
    sb.Append("\"deep_link_sub2\":\"1234\"").Append(",");//<REFERRER_ID(openid)>
    sb.Append("\"channel\":\"mobile_share\"").Append(",");//Channel
    sb.Append("\"campaign\":\"summer_sale\"");//Campaign
    // Other parameters, optional
    sb.Append("\"deep_link_sub3\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub4\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub5\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub6\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub7\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub8\":\"1234\"").Append(",");

    sb.Append("\"af_sub4\":\"12324\"").Append(",");
    sb.Append("\"af_sub5\":\"dfasdf\"").Append(",");

    sb.Append("}");
    String paramsJsonString = sb.ToString();
    INTLExtend.Invoke("AppsFlyer", "generateInviteLinkUrl", paramsJsonString);

Step 5: Test your OneLink integration

  1. Go to the AppsFlyer console.

  2. In the left sidebar, click SDK Integration Tests.

  3. Select the app to test and click Run test.

    Image: Integration Tests 1

  4. On the Run non-organic install test page, select the test device and select Other.

  5. Scan the QR code and install the app.

    Image: Integration Tests 2

note

To test the activation again, delete the app and restart the test.

Reference Docs

AppsFlyer deep linking and OneLink: Deep linking

iOSAndroid
iOS initial setupAndroid initial setup
iOS Unified Deep LinkingAndroid Unified Deep Linking
iOS Legacy APIsAndroid Legacy APIs
iOS Extended Deferred Deep LinkingAndroid Extended Deferred Deep Linking
iOS organic search attributionAndroid Organic Search Attribution

Client API

Deep link observer

Deep link observer handles the callback functions related to deep link data. For more information, see:

APIFunction
SetDeepLinkObserverSets the deep link callback, which notifies the game when data reaches the engine layer. When this callback is received, call the Fetch function to obtain the data.
RemoveDeepLinkObserverRemoves the deep link callback

Fetch

Fetch method returns the deep link data in the SDK cache. For more information, see:

APIFunction
FetchObtains the deep link data in the SDK cache.