Deep Linking

OneSignal's guide to deep linking for email, push, and in-app messaging channels.

Deep linking generally refers to clicking a link from outside your app (like through a website, email, or sms) which then opens your app and navigates to a specific page. If the link is clicked on a device without your app, it should take you to the app store to download it and if the link is clicked on a platform you do not support, it should redirect to a website.

There are many ways to setup deep linking and this guide shares recommendations for your push notifications, emails, in-app messages and sms.

📘

HTTPS URLs Required

In order to make these links open properly, you'll need to ensure that the link is being hosted on an HTTPS website, and that the site contains the Apple App Site Association file required for Apple, and the Asset Link file for Android.

Android Setup

Android Studio simplifies the deep linking process with App Links by providing a built in tool that will update your manifest and generate all of the link information and JSON file for you!

In Android Studio, go to "Tools" and select "App Links Assistant"

Follow each step to set up the link directly to the website and the information will be auto generated for you. The changes that are made include the following:

<activity  
android:name=".SecondActivity"  
android:exported="true">  
<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:scheme="https" />  
<data android:host="yoursite.com" /> 

Example Deep Link Activity Class

// ATTENTION: This was auto-generated to handle app links.  
Intent appLinkIntent = getIntent();  
String appLinkAction = appLinkIntent.getAction();  
Uri appLinkData = appLinkIntent.getData();

In order for the deep link to work properly, you'll need to host the file generated on step 3 from your website. The tool will generate all of the information for you and it should output a JSON object that includes a namespace, package_name, and sha256_certificate_fingerprints.

The File should be available when going to https://yoursite.com/.well-known/assetlinks.json

iOS Setup

Apple provides 2 common approaches to deep linking: Universal Links and URL Schemes.

Universal Links are Apple's preferred way to handle deep linking. However, Apple doesn't account for Universal Links used when the app is already open (like when clicking a push notification). Details on this in Apple's Developer docs on Universal Links here. Specifically this paragraph:

For this reason, it is recommended to use Universal Links for emails, sms, and other resources outside of your mobile app and URL Schemes for push notifications and in-app messages which trigger while the user is already within the app. However, if you already have Universal Links setup, this guide will explain how to use them for push and in-app messages if you choose to do so.

Xcode has a few built in tools to help us work with deep links. The one we'll be looking at for handling Universal Links, is going to be Associated Domains .

On your main target, select Signing and Capabilities and add Associated Domains. You will format your link here under domains by clicking the plus button and replacing the "https\://" of your website with "applinks:"

🚧

Update Default Launch URL Opening Behavior

When sending push with the Launch URL (API url property), OneSignal's iOS SDK uses the openURL method which is Apple's preferred way to handle URLs.

This will open your app, redirect to the browser, then deep link back into the app.

To prevent this behavior for supporting Universal Links, either use the Additional Data (API data property) or update your app's Info.plist file to include OneSignal_suppress_launch_urls to a Boolean type with Value YES. More details.

We can now add logic to our App Delegate to listen for when the NSUserActivity object is available. Here is an example that will just log out the link that is received, but if suppressing the default browser behavior, we can put logic in to link to a different view.

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
        if let webpageURL = userActivity.webpageURL {
            print("Received Universal Link: \(webpageURL)")
        }
        return true
    }

    return true
}

 
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {

    if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {

        NSURL *webpageURL = userActivity.webpageURL;

        NSLog(@"Received Universal Link: %@", webpageURL);

        return YES;

    }

    return YES;

}

Create a JSON file to serve from the website so that we can have a button shown to link us to the app when opened. The JSON object will need to contain your app's Bundle ID prefixed by your Team ID like so:

{ 
	"apps": [],
	"details": [ 
			{ 
				"appID": "TEAMID.com.example.deeplink",
				"paths": ["*"] 
			} 
		] 
	} 
}

This file should be available when navigating to https://\<your_url>/.well-known/apple-app-site-association

What should actually be hosted on the web server:

Your website should contain the correctly named paths that host the JSON files. These will be requested by Apple and Google. Those paths are:

  • /.well-known/apple-app-site-association
  • /.well-known/assetlinks.json

Below is an example of a node server that will serve these files when requested:

const http = require('http');
const url = require('url');

http.createServer((req, res) => {
  const requestUrl = url.parse(req.url, true);
  const path = requestUrl.pathname;

  // Check if the request is for the Android assetlinks.json file
  if (path === '/.well-known/assetlinks.json') {
    const assetLinks = [
      {
        "relation": ["delegate_permission/common.handle_all_urls"],
        "target": {
          "namespace": "android_app",
          "package_name": "com.example.deeplink",
          "sha256_cert_fingerprints": [
            "8A:4C:3D:A8:14:27:87:31:33:81:A7:65:C6:55......"
          ]
        }
      }
    ];

    // Set the Content-Type header and send the JSON response
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.end(JSON.stringify(assetLinks));
  } else if (path === '/.well-known/apple-app-site-association') {
    // Check if the request is for the apple-app-site-association file
    const aasa = {
      "applinks": {
        "apps": [],
        "details": [
          {
            "appID": "TEAMID.com.example.deeplink",
            "paths": ["*"]
          }
        ]
      }
    };

    // Set the Content-Type header and send the JSON response
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.end(JSON.stringify(aasa));
  } else {
    // Handle other routes or serve static files
    res.writeHead(404);
    res.end('Not Found');
  }
}).listen(3000, () => {
  console.log('Server listening on port 3000');
});


Send Push with a Deep Link

Since both Android and iOS are now set up to listen for this unique URL and will have the JSON files served when requested, we can just add the links either to the Launch URL (API url property) or Additional Data (API data property) when sending a push!

When clicking the notification on Android, we should see the app open directly to the activity that we added the App Links logic to. When clicking the iOS notification, we should see the browser open with a link that allows us to navigate back to the app (unless you are suppressing that behavior from the plist).


Send Email with Deep Links

Tracking email link click events requires changing the URL structure to capture the event, then redirect back to the original URL you set. This tracking causes issues with deep links.

By default, all emails sent from OneSignal will have link tracking enabled, causing this URL structure change.

You can disable link tracking by unchecking the Track link clicks option in the dashboard email form. For Emails sent through our API set disable_email_click_tracking to true in your API requests.

Track link clicks is enabled. Uncheck this box to disable link tracking.

Track link clicks is disabled.

If you disable link tracking:

  • OneSignal cannot track link clicks and will show N/A in the Email Message Reports Click-Through Rate.
  • All URLs in the email will not be tracked. There is not an option to disable link tracking for only specific links.

Email Deep Linking Behavior

Currently, if you setup deep links, you must disable email link tracking if you want that link to open your app directly instead of opening the browser first. This is because email links need to open in a browser which triggers the webhook to track the click event for that email. So the following scenarios can happen:

  1. iOS Universal Link, iOS Device, Browser is Safari, Enable Click Tracking => the link will open to Safari (triggers the click event), asks if you want to open the app, then direct you to the deep-linked page.
  2. iOS Universal Link, iOS Device, Browser is Safari, Disable Click Tracking => the link will open to the deep-linked page directly but no click event tracking.
  3. iOS Universal Link, iOS Device, Browser is not Safari, Enable or Disable Click Tracking => the link will open the browser and go to the app store.
  4. iOS Universal Link, Android Device, Enable or Disable Click Tracking => the link will open the browser and go to the app store.
  5. Android App Link, iOS Device, Browser is Safari, Enable or Disable Click Tracking => the link will open the browser and go to the app store.
  6. Android App Link, Android Device, Enable Click Tracking => the link will open the browser (triggers the click event), ask if you want to open the app, then direct you to the deep-linked page.
  7. Android App Link, Android Device, Disable Click Tracking => the link will open to the deep-linked page directly but no click event tracking.

Send In-App Messages with Deep Links

To deep link into another page of the app, you can use the URL or Custom Action ID click action.

Depending on the type of deep link you setup, you can select the Custom Action ID click action and set the the deep link URI into the Action Name field.

Upon the block getting clicked, this Action Name will be available within the OneSignal SDK IAM Click Event listener within the actionId property. This is what your payload would contain:"result": {"actionId": "your URI", ... }

OneSignal.getInAppMessages().addClickListener(new IInAppMessageClickListener() {
    @Override
    public void onClick(@Nullable IInAppMessageClickEvent event) {
        if(event.getResult().getActionId().equals("https://your_url_here")){
            //Redirect your user here
        }
    }
});

val clickListener = object : IInAppMessageClickListener {
    override fun onClick(event: IInAppMessageClickEvent) {
        print(event.result.actionId)
    }
}

OneSignal.InAppMessages.addClickListener(clickListener)
class AppDelegate: UIResponder, UIApplicationDelegate, OSInAppMessageClickListener {
...
  OneSignal.InAppMessages.addClickListener(self)

  func onClick(event: OSInAppMessageClickEvent){
        let actionId = event.result.actionId
        if actionId == "https://your_url_here" {
            //Redirect your user here
        }
    }
}
@interface AppDelegate () <UIApplicationDelegate, OSInAppMessageClickListener>
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...
  // Add your object as a listener
  [OneSignal.InAppMessages addClickListener:self];
  ...
  return YES;
}

// Add this method to object implementing the OSInAppMessageClickListener protocol
- (void)onClick:(OSInAppMessageClickEvent *)event {
    NSString *actionId = event.result.actionId;
    if ([actionId isEqualToString:@"https://your_url_here"]) {
        // Redirect your user here
    }
}



Within the event listener, you can handle your deep link as needed.