Using Mobile Universal and App Links with SparkPost

November 12, 2020 Contributors


Email is often read and engaged with on mobile devices. Both iOS (9 and later) and Android (6.0 and later) provide a way for links in your content to take your users directly into an app that you have created, providing your users with a richer interactive experience.
The links that connect a user directly to your app are referred to as deep links.

This article describes how to configure your content and mobile apps to support deep links.

iOS uses the term Universal Link. The Android term is "App link". We use the term "deep link" in this article to cover the aspects common to both.

When correctly configured, a deep link takes your user directly from the email to your app, without opening the mobile web browser. If the app is not installed, the mobile web browser will open and display the specified page instead.

Setup requirements

  • Known URL path(s) that you want to deep link to your application.

    • When using SparkPost Click Tracking, you choose the Tracking Domain and the custom link sub-path, explained here. It’s best to choose a subdomain (e.g., so that subdomain can be redirected, while your website uses your organizational domain.
  • Deep linking spec files hosted on your website or CDN, in the correct location for the devices to find. The spec file declares the identity of your app, and which URL paths lead to your app – described here. The spec files must be accessible via HTTPS.

  • A mobile app that is registered to handle incoming deep link requests – see example code.

The deep link will operate only when all of these match correctly. When you’re ready, check your setup against the summary of setup steps.

If you are using a deep linking platform such as or AppsFlyer, these platforms automate some of the setup – see here.

Writing your mobile app

The following developer documentation explains the deep linking mechanisms in detail.

Deep linking spec files

The spec files must be published on your domain(s) and accessible via HTTPS. They may be hosted on a regular web server, or via a CDN – examples here.

  • For iOS devices, the file is named apple-app-site-association
  • For Android devices, the file is named assetlinks.json

These files should be placed on your website in the directory .well-known.

iOS: example apple-app-site-association

  "applinks": {
    "apps": [],
    "details": [
        "appID": "",
        "paths": [

The appID is available from your XCode development environment, or from your Apple Developer Account, and follows a specific format:

  • A hex prefix (e.g. ABCD1234)
  • A "team ID" or "bundle ID" that identifies the developer of the app (e.g. com.mycompany)
  • The app name (e.g. testlinks).

Your app needs access to the Apple "associated domains" entitlement. You need a paid Apple Developer account for this. Set up the "associated domains" to match your paths.

Configure the paths section to match the links in your email HTML content, depending on your chosen SparkPost click tracking setup (explained here).

Android: example assetlinks.json

  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.testlinks",

The SHA256 fingerprint is unique to your particular app.

The App Links Assistant (Under the Tools menu) helps you configure the associated domains and permissions for your app, and generate the assetlinks.json file. It guides you step-by-step.

  1. Add URL intent filters

    Add your tracking domain URLs with Path set to "pathPrefix", and add your chosen prefix starting with /f/, for example /f/open-in-app. Your app can be registered to multiple URLs if you wish.

    Use the "Check URL mapping" box to test that your mapping works with a real URL.

  2. Add logic to handle the intent

    This works for Java apps but not currently for Kotlin – see here for a Kotlin example.

  3. Associate website

    Enter your site domain.

    Choose "Signing config". During development, "debug" is fine.

    Save your digital asset links file.

  4. Test on Device or Emulator

    Test that your URL triggers your app.

  5. Deploy your app.

    On first open, by default, your recipients will be prompted to select whether to select your app. Android remembers the user’s preference.

  6. In AndroidManifest.xml, you can set your domain intent-filter to autoVerify to favor opening your app instead of asking the user – see here.

    <intent-filter android:autoVerify="true">

    This requires your assetlinks.json file to be hosted on your specific tracking domain. This is explained further in the hosting and troubleshooting sections.

Setting up a custom tracking domain is useful for branding and email reputation. It is required for click tracking of deep links in your HTML email.

It’s best to choose a subdomain (e.g., so that subdomain can be redirected, while your website uses your organizational domain. Each tracking domain can serve regular email tracked links and deep links. Your content can use a mixture of both kinds of link inside the same email.

It is good practice to use secure (HTTPS) tracking domains, and it’s essential if your website has an HSTS policy. We have articles describing the setup of this for:

In both cases, your CDN (or proxy) redirects the specific tracking subdomain(s) to the SparkPost click tracking service, while your web content is served in the usual way.

How click tracking works

It might be useful to understand how the SparkPost click tracking feature works with HTML links. Other email-service-provider click tracking services work in a similar way.

When SparkPost encounters an anchor tag in an HTML email, it will replace the href attribute with a new URL pointing to the SparkPost click tracking service. When your recipient clicks that link, the SparkPost service receives the request (via CDN for HTTPS links), records the click, and redirects the recipient to your original URL.

Normal click tracking (without deep links) – icons by The Noun Project

Deep links supersede this flow; after checking the spec file, the device passes the request directly to your app instead. This means your app will receive the tracked link via an API call and can show the user an appropriate part of your app.

SparkPost won’t receive the request and won’t register the click in your analytics, unless your app code makes it happen.

A workaround is to disable tracking for deep links. This may be appropriate if you already have analytics for measuring in-app activity.

The preferred solution is to set up Sparkpost click tracking and include code in your app to follow the link. We’ll review each of these.

If you disable SparkPost’s click tracking for the mobile deep links in your email, they work just fine with your mobile app with no further effort. This works because SparkPost does not alter untracked links in your email.

The "path" in your Apple spec file needs to match your iOS app "associated domains".

If you’re using the SparkPost REST API, you can use the data-msys-clicktrack attribute to disable click tracking for a single link in your email:

<a data-msys-clicktrack="0" href="">Open in app</a>

You can also disable click-tracking on plain-text email content as follows. See this article for more information.[[data-msys-clicktrack="0"]]

This is appropriate where your content has a mix of deep links and links leading to an ordinary website destination.

Alternatively, you can disable click tracking for a whole transmission request by setting the click_tracking option to false. The SMTP API supports a similar flag.
You can control click tracking for SMTP messages at the account level too.

Deep link flow, app triggers click tracking – icons by The Noun Project

The device operating system "wakes up" your app with an API call, passing in the URL. Your app issues an HTTP(S) GET to the URL, which registers the click. Your app receives the original URL in the response "Location" header; there’s no need to follow the redirect and fetch the entire web-page.

Set up your spec files to match your custom tracking domain and custom link sub-path.

Your spec file states which link paths should be treated as deep links. Any other paths on that domain will be opened via the device web browser in the usual way. This enables you to use both regular tracked links and deep links with that tracking domain. Deep links are distinguished from regular tracked links as follows.

SparkPost will include a specific string in the path part of a tracked URL when your content has the data-msys-sublink attribute, for example:

<a href="" data-msys-sublink="open-in-app">Open in app</a>

This will cause the tracked link path to start with /f/open-in-app/ which simplifies writing your apple-app-site-association file.

SparkPost supports the use of custom sub-paths on an individual link basis. This allows you to choose, when setting up your spec file, which app opens depending on the custom subpath.

You can also set up custom sub-paths in plain text email content as follows. See this article for more information.[[data-msys-sublink="open-in-app"]]

If a custom sub-path is not used, the spec files can be configured to open on a wildcard path.

Examples for iOS and Android are shown here.

Summary of setup steps

  1. Configure your CDN or reverse proxy to host your custom tracking domain.

  2. Configure and verify a custom tracking domain on your SparkPost account.

  3. Choose the link sub-path(s) to be used for deep linking.

  4. Ensure the spec files are set up for your tracking domain(s) and link sub-path(s).

  5. Test that your spec files are accessible via HTTPS.

  6. Send your email through SparkPost with click tracking enabled, using your custom tracking domain and link sub-path:

    Your invoice is available <a data-msys-sublink="open-in-app" href="">here</a>.
  7. Check the fallback cases:

    • the link opens correctly via desktop browser.
    • on a mobile device without your app installed, the link opens via browser.
  8. Check the link launches your app on mobile devices.

    To enable SparkPost to track deep links:

  9. Have your mobile app code make an HTTP GET request to the SparkPost click tracking service when it handles the incoming click event. Details and sample code for this below.

  10. Have your app take action based on the original URL which SparkPost returns in its "3xx redirect" HTTP response.

Example application code

iOS – Swift – forwarding clicks to SparkPost

In your app AppDelegate.swift, in class AppDelegate, add a handler for an NSUserActivity:

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    print("Continue User Activity called: ") // debug
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
        let url = userActivity.webpageURL {

        // Follow the link to trigger click tracking

        let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
            guard let originalURL = response?.url else {
                print("Unexpected URL \(url.absoluteString)")
    return true

This code calls the SparkPost click tracking service, which responds with a "3xx" redirect to the original URL from your email, which can be used by your app.

This simple code gets the Location header and also follows the redirect to fetch the web-page, which consumes device bandwidth. It’s more efficient, but needs more code to stop after the "302" response is received; complete example iOS app here.

iOS User Agent

In SparkPost event reporting, the attribute user_agent carries information on app name and OS version, for example "testlinks/1 CFNetwork/1197 Darwin/20.0.0". This enables you to use SparkPost analytics to find out which links are being opened via your app.

Forwarding Clicks From Android To SparkPost

When an Android email client recognizes that an app link has been clicked based on your app AndroidManifest.xml, it sends an intent which triggers the registered activity in your app. Your app can then make the HTTP(S) request, to register the "click" event in SparkPost and retrieve the original tracked URL from the message.

Here is sample MainActivity.kt which uses the OkHttp4 library to perform the HTTP(S) GET to the SparkPost click tracking service asynchronously, triggering the onResponse or onFailure functions on completion.

The incoming URL and the original unwrapped URL are shown on the app TextView elements.

package com.example.testlinks

// Example deep linking app

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import okhttp3.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {


    override fun onNewIntent(intent: Intent) {
        Log.i("MainActivity", "onNewIntent called")

    private fun handleIntent(intent: Intent) {
        Log.i("MainActivity", "handleIntent called")

        val appLinkAction = intent.action
        val appLinkData: Uri? =
        if (Intent.ACTION_VIEW == appLinkAction) {
            // handle URL
            val res : TextView = findViewById(
            res.text = appLinkData.toString()

            // Show this on our simple example app. The function updates the TextView itself

    // Basic URL GET request.
    // See

    private fun makeRequest(url: Uri?) {
        // More efficient click-tracking with HTTP GET to obtain the "302" response, but not follow the redirect through to the Location.
        val client = OkHttpClient.Builder()

        val request = Request.Builder()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {

            override fun onResponse(call: Call, response: Response) {
                val locationURL = response.headers["Location"]
                if (locationURL != null) {
                    Log.i("locationURL", locationURL)
                    // Show this on our simple example app
                    val originalUrl : TextView = findViewById(
                    originalUrl.text = locationURL

Android Kotlin example

The makeRequest function gets the Location header without following the redirect, which saves bandwidth and battery power. See here for the complete example app.

OkHttp library dependency

In the project app/build.gradle file dependencies section, this line fetches and builds the library. We used this library because it gives the required control over the HTTP(S) stack to control how redirects are handled.

implementation 'com.squareup.okhttp3:okhttp:4.9.0'

Your AndroidManifest.xml also needs these permissions added to <manifest .. > scope:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Android User Agent

In SparkPost event reporting, the attribute user_agent carries information on your app. In our demo app, it will be the default set by the library: "okhttp/4.9.0". This enables you to use SparkPost analytics to find out which links are being opened via your app. You can customise this in your code.

Hosting the spec files

You can use a CDN or an ordinary web-server to host your spec files. Ensure you have a valid certificate for your domain, as devices need to fetch these files using HTTPS.

When set up, you can get a security report by running the SSL Labs server test.

Step-by-step instructions follow, for


  1. Find the directory used by your web server, known as the web root. The default is /var/www/html, but yours may vary. You can find this by locating the DocumentRoot setting in your .conf files.

  2. Within this, create a directory .well-known if it doesn’t already exist, and upload/create your spec files here. This will usually require root privilege on your server.

  3. Restart Apache.

  4. You can check they are served using your web browser.

Checking spec files

Click the padlock symbol and check the certificate is valid and as expected. Repeat for the Android assetlinks.json file.

Using specific tracking domain(s) with Apache

The above simple instructions allow deep linking to work via your website’s main /.well-known URL.

To get Android to autoverify your app’s domains (skipping the user "Ask" step), you need to serve spec files from your specific tracking domains, while also forwarding opens and clicks to SparkPost on that domain. Follow the steps in this article, then modify the Apache patterns to look like this:

# Reverse proxy for SparkPost engagement tracking, and enable specific files to show through
<VirtualHost _default_:80>
  ProxyPass "/f/" ""
  ProxyPassReverse "/f/" ""
  ProxyPass "/q/" ""
  ProxyPassReverse "/q/" ""

  Alias "/.well-known" "/var/www/html/securetrack/.well-known"

<VirtualHost _default_:443>
  ProxyPass "/f/" ""
  ProxyPassReverse "/f/" ""
  ProxyPass "/q/" ""
  ProxyPassReverse "/q/" ""

  Alias "/.well-known" "/var/www/html/securetrack/.well-known"

  SSLEngine on
  SSLCertificateFile "/opt/apache2/conf/server.crt"
  SSLCertificateKeyFile "/opt/apache2/conf/server.key"
  SSLProxyEngine on

The paths /f/ and /q/ are SparkPost tracked clicks and opens, respectively.

The spec files are made available under URL from a specific directory using the Alias directive. If you want your files to be identical to your main website, then you can omit this step.

For completeness, the same routing is configured for HTTP on port 80, although mobile devices will request the spec files via HTTPS.

To check your files are served correctly and Android autoverify is working – see troubleshooting tips.


  1. Follow the steps in this article to set up your secure tracking domain.

  2. Check where your NGINX root directory is, by running nginx -V. Look for the path shown after --prefix=, for example --prefix=/usr/share/nginx (and adjust the following example to suit). This should contain a directory named html which is used for your files.

  3. Within this, create a directory .well-known if it doesn’t already exist, and upload/create your spec files here. This will usually require root privilege on your server.

  4. Add location blocks to your config to declare the spec files on your tracking domain, which will allow Android to autoverify. Here is a complete example, including the engagement-tracking proxy-pass block done in step 1.

    server {
        listen 80;
        listen 443 ssl http2;
        ssl_certificate /etc/letsencrypt/live/;
        ssl_certificate_key /etc/letsencrypt/live/;
        # Security improvements - needed to get an "A" rating
        ssl_protocols TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
        # Serve the deep linking spec files using specific patterns
        location = /.well-known/apple-app-site-association {
            root /usr/share/nginx/html;
            default_type "application/json";
        location = /.well-known/assetlinks.json {
        root /usr/share/nginx/html;
        default_type "application/json";
        # pass all other requests through to SparkPost engagement tracking
        location / {
            proxy_set_header X-Forwarded-For $remote_addr; # pass the client IP to the open & click tracker
            server_tokens off; # suppress NGINX giving version/OS information on error pages

    Note the security settings to use only newer TLS versions and disable weaker ciphers, to get an "A" rating on the SSL Labs server test. You may wish to adjust these to suit your own IT policy, e.g. to accept TLS v1.1 as well for compatibility with older email clients / web browsers.

  5. Check your configuration is valid using sudo nginx -t. If no errors are reported, then reload using sudo nginx -s reload.

  6. Check your files are served correctly and Android autoverify is working – see troubleshooting tips.

AWS CloudFront

As described here, it’s easy to create spec files in your web site. The following steps are needed only if you are using a CDN for HTTPS tracking and therefore need to configure the spec files there.

First set up your secure tracking domain using CloudFront – instructions here. This establishes your tracking domain routing and certificate in AWS. This section describes how to:

  • Create an S3 bucket for the spec files;
  • Set up our CloudFront distribution to selectively serve requests on our tracking domain from the bucket;
  • Test the files are served correctly on the specific tracking domain;
  • Ensure our app domain association matches our tracking domain.

With CloudFront we are working with the specific sub-domain used for link tracking. It’s therefore an alternative to updating your main website’s .well-known directory.

Create S3 bucket

  1. On AWS S3, create a new bucket if you don’t already have a bucket for your website. Give it a name, and choose region.

    • Step 2, "Congfigure options", you can leave these options unset.

    • Step 3, "Set permissions", leave this at default ("Block all") for now.

    • Step 4, "Review", choose "Create bucket". This returns you to the S3 console.

  2. On your bucket permissions tab, select Edit, then uncheck "block all public access" and select "Save", then "confirm". Refer to AWS documentation if needed.

    Note: you don’t need to create a Bucket Policy at this stage, because CloudFront creates a specific policy for you in the next step.

    There is also no need to enable "Static website hosting", as CloudFront will selectively match specified URL path patterns to serve your files.

  3. Select your bucket. If you don’t already have a folder named .well-known, then create it.

    Leave security settings at "None (Use bucket settings)" and select Save.

  4. Select your folder, then select "Upload". Drag and drop in your spec files.

    Select Next. Leave access permissions for your user ID at their defaults, and select Next.

    Choose "Standard" storage class and select Next. After review, complete the upload.

    You should now have two files in your bucket.

  5. Select both files via the check-boxes. Under the "Actions" drop-down menu, select "Change metadata". Under "Select a key", choose Content-Type, and type in the value application-json. Select "Save" then "Change".

    Your files are now uploaded in S3.

Set up AWS CloudFront to serve your spec files

  1. On AWS CloudFront, under "Origins and Origin Groups", select "Create Origin".

    • In Origin Domain Name, start typing the name of your S3 bucket, then choose it from the drop-down list presented.

    • Leave Origin Path blank, as we have created the S3 bucket with the correct folder-name to fit the incoming path already.

    • On Origin Access Identity, choose "Create a New Identity" if you don’t have one already.

    • On Grant Read Permissions on Bucket, choose "Yes, Update Bucket Policy".

    • Leave the other settings at defaults. Scroll down and select "Create".

  2. Set up which URL paths will be forwarded by CloudFront to the files in your bucket.

    Select your distribution by clicking on its name. Select the "Behaviors" tab and then "Create Behavior".

    • In Path Pattern, type .well-known/*

    • In Origin or Origin Group, select your S3 bucket from the drop-down list.

    • In Viewer Protocol Policy, select "HTTPS Only" (as mobile devices will always request using HTTPS).

    • Leave the remaining settings at default. At the bottom of the page, select "Create".

    • Back on the CloudFront Behaviors list view, you should see your new rule has been created.

  3. Return to the CloudFront Distributions list, you should see Status "In Progress" as the distribution is deployed.

  4. Check your S3 bucket configuration is secure using this tool.

  5. To check your files are served correctly and Android autoverify is working, see troubleshooting tips.


As described here, it’s easy to create spec files in your web site. The following steps are needed only if you are using a CDN for HTTPS tracking and therefore need to configure the spec files there.

Unlike AWS CloudFront, you need to already have the spec files (apple-app-site-assocation and assetlinks.json) hosted elsewhere, such as on a web server. When your clients request*, CloudFlare responds with a 301 "moved permanently" redirect to your files. We have found this can work, but it’s not recommended by Apple or Google. It prevents Android autoverifying your app.

  1. In your CloudFlare dashboard, an additional page rule is necessary to serve the spec files.

    • Page Rules Tab -> Create Page Rule

    • Enter your domain without the https prefix, like so:*

    • Add a Setting -> Forwarding URL (specify a 301 redirect option)

    • Destination URL is determined by where the spec files are hosted. The destination URL should be configured as https://<DEEP_LINK_DESTINATION>/.well-known/$1. (The $1 placeholder contains the file name matched by the * in the domain rule.)

    • CloudFlare page rules are evaluated in priority order. Set this page rule to be first, with the page rules for forwarding to SparkPost Engagement Tracking afterwards.

  2. Your rules should now look like this.

  3. To check your files are served correctly – see troubleshooting tips.


As described here, it’s easy to create spec files in your web site. The following steps are needed only if you are using a CDN for HTTPS tracking and therefore need to configure the spec files there.

Unlike AWS CloudFront, you need to already have the spec files (apple-app-site-assocation and assetlinks.json) hosted elsewhere, such as on a web server. Fastly can serve requests for these files without sending the client a 301 "moved permanently" redirect, so it supports Android autoverifying your app.

  1. Set up your secure tracking domain – instructions here. This establishes your tracking domain routing and certificate in Fastly.

  2. "Clone" your current configuration. This gives a draft config to edit. The version number increments, while your previous version continues to be active.

    The following steps should be done on your new draft config.

  3. Select Origins / Hosts, then Create a Host.

    Enter the address of your website where the spec files will be served. Use the pencil "edit" icon to give your host a meaningful name.

  4. Select "Attach a condition", give this a name, such as "well-known". In the "Apply if …" box, enter the following. The ~ applies a Perl-like regular-expression match on the incoming URL path.

    req.url ~ "^/\.well-known/.*"

    Leave the priority settings as default.

  5. Your host settings should now look like this.

  6. Select "Activate" to make your new configuration live.

  7. Check your files are served correctly – see troubleshooting tips.

Background info: VCL, Fastly boilerplate and "Fiddle"

Fastly is based on Varnish HTTP accelerator, and uses Perl-like Varnish Configuration Language (VCL) to configure forwarding rules. Your entire configuration is a VCL file, which you can view using the "Show VCL" option. The condition you created appears in a section called

sub vcl_recv {


For more advanced VCL capabilities, start with the "boilerplate" code. Edit, then upload it back.

You can also prototype VCL separately from your main configuration, using the online Fastly Fiddle tool; see this article. You enter the origin servers, custom VCL code and test requests.

When you run the request, the forwarding decision and result is shown; in this case, fetching the Apple spec file from an Apache server.

Likewise, if you run a request for a tracked link from a test email sent through your SparkPost account (with a path starting /f/), you should see your VCL code forward the request to SparkPost.

Alternative setup with deep linking platforms

If you are using a deep linking platform such as or AppsFlyer, these platforms automate some of the setup process for you, while also working in a specific way.


Branch is a mobile linking platform powering deep links and mobile attribution, supporting web and other clients as well as iOS and Android. It provides an SDK which you need to include into your app – see here for specific steps for the platforms you’re using.

Universal Email overview is an introduction to the how Branch handles links in email.

Branch has built-in integrations for many email service provider setups including SparkPost, Braze + SparkPost, BlueShift + SparkPost, listed here. Each of these follow the same standard Univeral Email Integration Guide.

Branch can host your secure Click Tracking Domain (CTD) and spec files, with a certificate for HTTPS access. However Branch also has specific deep linking features:

  • Branch creates the spec files (apple-app-link-association, assetlinks.json) via configuration in your account, and hosts them on your click tracking domain(s) automatically.
  • Your app should include and use the Branch SDK to accept incoming click events and resolve links for your app, rather than native code.

Branch also has a special link format, which can be used for testing with your app prior to testing with tracked email links. You choose a unique name for your This name differs from your email Click Tracking Domain, and is specific to your Branch account.

Here are the steps to get your app running with Branch and SparkPost. Your source of help during this process is the Branch online documentation and support system. The first steps are independent of email provider, and are done with your Branch account, code development enviroment, and Branch SDK.

  1. Download and integrate the Branch SDK into your apps (iOS, Android). Ensure your app has the expected code for receiving incoming events from the Branch SDK. Ensure your app is configured with your Branch account settings. Ensure your Branch account is configured with your app ID & Bundle ID.

  2. Create a test message containing your on your test device (e.g. using iMessage or email) and check that your app opens and receives the event. It may be helpful to print received event parameters to the console during development.

  3. Follow the Branch Univeral Email Integration Guide to set up your Click Tracking Domain(s) in the Branch dashboard.

    This example shows the standard SparkPost US endpoint address. Be sure to use the address for your SparkPost account region and type, see here.

  4. The guide directs you to "Point DNS CNAME to Branch". Once this is done, follow the steps here to set your tracking domain in SparkPost to Secure mode and verify, if not verified already.

  5. You can now send email using the Branch-hosted Click Tracking Domain for your links. These should open as usual on a desktop browser, and open your app on mobile devices.

The Branch dashboard shows the status of your email integration.

Clicking the gearwheel shows:

The troubleshooting tips below can also be used. For example, you can check your spec files are present on your Click Tracking Domain, and check your tracked links are resolving through Branch to SparkPost’s endpoint.

Troubleshooting tips

Check your spec files

You can check your spec files are correctly published and available on the Internet, using curl or a web browser. Be sure to specify https.

curl https://your-tracking-domain-here/.well-known/apple-app-site-association

curl https://your-tracking-domain-here/.well-known/assetlinks.json

Note: If your tracking sub-domain does not have spec file(s), mobile devices will also look on the root domain for files in the /.well-known directory. This is shown in the above simple Apache example.

You can view your encoded links using Gmail, by selecting the three dots menu top-right, then "Show Original". Check the domain is as expected, and the <A HREF .. links are using HTTPS. Check that clicking the link takes you to the expected landing page.

You can then use curl to request the tracked link, with the -v option to see the details of the tracked link request and response; see here.

Check your app matches your tracking domain

The domains entitlement in your app(s) must match your tracking domain. This can be done specifically, or (with Apple) a wildcard matching sub-domain. Refer to

Android: testing autoverify

Getting your app to autoverify requires the assetlinks.json file to be available on your specific tracking domain rather than relying on the organizational domain (main website).

To test autoverify works for your domain(s), start with a fresh install of your app, as mentioned in this article, because Android remembers the user’s choice.

If you are getting the "ask" prompt to choose which application opens the link, you can use the adb debugger to investigate the status of your app. If the Status is shown as ask then autoverify is not working as intended. The value always : 200000000 means it is verified.

$ adb reconnect
reconnecting emulator-5554 [device]

$ adb shell dumpsys package d
App verification status:
  Package: com.example.testlinks
  Status:  always : 200000000

If your app has more than one associated domain, the status will be "ask" if any of them fail to autoverify. You can use adb to check your setup from Android Studio without having to delete/reinstall. For more information on using the adb debugger, see this article.

Note: CloudFlare CDN is problematic for Android autoverify, owing to its use of 301 redirect for files.

Further reading

  1. article on setting up iOS Universal Links
  2. Apple article on Universal Links including info on MacOS and WatchOS as well as iOS
  3. Tips on iOS app debugging with Universal Links and the XCode device simulator
  4. Apple WWDC 2020 presentation
  5. More on AWS CloudFront Distributions
  6. View email internals including tracked links, with Gmail Show Original
  7. NGINX Location block
  8. Understanding and Configuring Cloudflare Page Rules
  9. Android App Links documentation on auto-verify
  10. Article on Android deep links the wrong and right way (specifically, auto-verify on variations of your different domains)
  11. Article on investigating Android deep-link problems with adb
  12. Android Studio App Links Assistant tool