Blueshift universal links (Android)
Set up universal links to open your Android app directly from emails or web messages.
Blueshift universal links enable users to navigate directly from emails or web messages to specific screens within your Android app. When a user taps a universal link, Android launches your app instead of opening a browser.
Prerequisites
Before setting up universal links, ensure you have completed:
- SDK setup
CNAMEdomain configuration (contact the Blueshift implementation or support team)
Setup
Step 1: Generate SHA-256 certificate fingerprint
SHA-256 certificate fingerprintYour SHA-256 fingerprint is required to create the assetlinks.json file.
To get your SHA-256 certificate fingerprint, run this command in your terminal:
keytool -keystore <your_keystore_path> -list -v
Copy the SHA-256 value from the output. It will look like:
SHA-256: AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90
Create the assetlinks.json file:
Create a file named assetlinks.json with this content:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.yourapp",
"sha256_cert_fingerprints": [
"AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90"
]
}
}]
Replace:
com.example.yourappwith your actual package name- The fingerprint value with your
SHA-256from the keytool command
Note: Keep the colon-separated format for the fingerprint as shown above.
For multiple signing keys (debug and release)
If you have separate debug and release signing keys, include both fingerprints:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.yourapp",
"sha256_cert_fingerprints": [
"DEBUG_KEY_FINGERPRINT_HERE",
"RELEASE_KEY_FINGERPRINT_HERE"
]
}
}]
Step 2: Submit assetlinks.json to Blueshift
- Sign in to Blueshift.
- Navigate to the top-right corner, click the hamburger menu (☰), and select Account Settings.
- Select the Other Settings tab.
- In the Asset Links File field, paste the contents of your
assetlinks.jsonfile.
The file should include:
- Your app's package name
- Your SHA-256 certificate fingerprint
For more information, see Declare website associations on the Android developer portal.
Step 3: Configure intent filters
Add intent filters to your activity in AndroidManifest.xml:
<activity android:name=".MainActivity">
<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"
android:host="links.clientdomain.com"
android:pathPrefix="/track" />
<data
android:scheme="https"
android:host="links.clientdomain.com"
android:pathPrefix="/z" />
</intent-filter>
</activity>
Replace links.clientdomain.com with your actual CNAME domain that points to links.getblueshift.com.
Note:
/trackhandles tracking and analytics links.
/zhandles deep links that open specific screens in your app.If a link isn’t opening the app, make sure the URL path matches one of these and is listed in your
AndroidManifest.xml.
Handle deep links
There are two ways to handle universal links in your app.
Option 1: Override BlueshiftLinksActivity
Create an activity that extends BlueshiftLinksActivity:
import com.blueshift.BlueshiftLinksActivity
import com.blueshift.BlueshiftLinksListener
import android.net.Uri
import android.util.Log
class MyLinksActivity : BlueshiftLinksActivity() {
override fun getBlueshiftLinksListener(): BlueshiftLinksListener {
return object : BlueshiftLinksListener {
override fun onLinkProcessingStart() {
Log.d("DeepLink", "Link processing started")
}
override fun onLinkProcessingComplete(uri: Uri?) {
Log.d("DeepLink", "Received URI: $uri")
// Navigate based on the URI
when (uri?.path) {
"/products" -> {
// Navigate to products screen
}
"/cart" -> {
// Navigate to cart screen
}
}
}
override fun onLinkProcessingError(e: Exception?, link: Uri?) {
Log.e("DeepLink", "Error: ${e?.message}")
}
}
}
}
import com.blueshift.BlueshiftLinksActivity;
import com.blueshift.BlueshiftLinksListener;
import android.net.Uri;
import android.util.Log;
public class MyLinksActivity extends BlueshiftLinksActivity {
@Override
protected BlueshiftLinksListener getBlueshiftLinksListener() {
return new BlueshiftLinksListener() {
@Override
public void onLinkProcessingStart() {
Log.d("DeepLink", "Link processing started");
}
@Override
public void onLinkProcessingComplete(Uri uri) {
Log.d("DeepLink", "Received URI: " + uri);
}
@Override
public void onLinkProcessingError(Exception e, Uri link) {
Log.e("DeepLink", "Error: " + e.getMessage());
}
};
}
}
Register this activity in AndroidManifest.xml:
<activity
android:name=".MyLinksActivity"
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"
android:host="links.clientdomain.com"
android:pathPrefix="/track" />
<data
android:scheme="https"
android:host="links.clientdomain.com"
android:pathPrefix="/z" />
</intent-filter>
</activity>
Option 2: Use handleBlueshiftUniversalLinks()
Call the SDK's handleBlueshiftUniversalLinks() method from any activity:
import com.blueshift.BlueshiftLinksHandler
import com.blueshift.BlueshiftLinksListener
import android.os.Bundle
import android.net.Uri
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
class MyLinksActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
BlueshiftLinksHandler(this).handleBlueshiftUniversalLinks(
intent,
object : BlueshiftLinksListener {
override fun onLinkProcessingStart() {
Log.d("DeepLink", "Link processing started")
}
override fun onLinkProcessingComplete(uri: Uri?) {
Log.d("DeepLink", "Received URI: $uri")
}
override fun onLinkProcessingError(e: Exception?, link: Uri?) {
Log.e("DeepLink", "Error: ${e?.message}")
}
}
)
}
}
import com.blueshift.BlueshiftLinksHandler;
import com.blueshift.BlueshiftLinksListener;
import android.os.Bundle;
import android.net.Uri;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MyLinksActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new BlueshiftLinksHandler(this).handleBlueshiftUniversalLinks(
getIntent(),
new BlueshiftLinksListener() {
@Override
public void onLinkProcessingStart() {
Log.d("DeepLink", "Link processing started");
}
@Override
public void onLinkProcessingComplete(Uri uri) {
Log.d("DeepLink", "Received URI: " + uri);
}
@Override
public void onLinkProcessingError(Exception e, Uri link) {
Log.e("DeepLink", "Error: " + e.getMessage());
}
}
);
}
}
Register this activity in AndroidManifest.xml as shown in Option 1.
Testing universal links
Step 1: Verify intent filter configuration
Build and install your app, then test that your activity receives deep link intents.
Add a custom scheme to your intent filter for local testing:
<activity android:name=".MyLinksActivity">
<!-- Custom scheme for local testing -->
<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="yourappname" />
</intent-filter>
<!-- HTTPS intent filter for production -->
<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"
android:host="links.clientdomain.com"
android:pathPrefix="/track" />
</intent-filter>
</activity>
Test with ADB (Android Debug Bridge - a command-line tool for communicating with Android devices):
adb shell am start -a android.intent.action.VIEW -d "yourappname://test"
Check Logcat:
adb logcat | grep "DeepLink"
Expected output:
D/DeepLink: Link processing started
D/DeepLink: Received URI: yourappname://test
If you see an error like "No redirect URL (redir) found", this is expected for test links. The activity is working correctly - genuine Blueshift links will include the required parameters.
Step 2: Verify assetlinks.json is hosted
Before testing production universal links, confirm Blueshift is hosting your assetlinks.json file.
Open your browser and visit:
https://links.clientdomain.com/.well-known/assetlinks.json
Replace links.clientdomain.com with your CNAME domain. You should see JSON output without requiring authentication.
Step 3: Test with production universal link
Once your CNAME and assetlinks.json are configured:
- Create a test campaign in Blueshift with a universal link using your CNAME domain
- Send the campaign to your test device email
- Open the email on your device
- Tap the link
Expected behavior:
- App launches (or comes to foreground if already open)
- Your
MyLinksActivityprocesses the link - Logcat shows "Link processing started" and "Received URI"
Check Logcat:
adb logcat | grep "DeepLink"
If the link opens in the browser instead of the app:
- Android may not have verified domain ownership yet
- Reinstall the app to retry verification
- Verify assetlinks.json is accessible and correctly formatted
Advanced
Extract query parameters
override fun onLinkProcessingComplete(uri: Uri?) {
val productId = uri?.getQueryParameter("product_id")
val category = uri?.getQueryParameter("category")
val intent = Intent(this, ProductDetailActivity::class.java).apply {
putExtra("PRODUCT_ID", productId)
putExtra("CATEGORY", category)
}
startActivity(intent)
finish()
}
@Override
public void onLinkProcessingComplete(Uri uri) {
String productId = uri.getQueryParameter("product_id");
String category = uri.getQueryParameter("category");
Intent intent = new Intent(this, ProductDetailActivity.class);
intent.putExtra("PRODUCT_ID", productId);
intent.putExtra("CATEGORY", category);
startActivity(intent);
finish();
}
Custom navigation logic
override fun onLinkProcessingComplete(uri: Uri?) {
when {
uri?.path?.startsWith("/products") == true -> {
startActivity(Intent(this, ProductsActivity::class.java))
}
uri?.path?.startsWith("/cart") == true -> {
startActivity(Intent(this, CartActivity::class.java))
}
else -> {
startActivity(Intent(this, MainActivity::class.java))
}
}
finish()
}
@Override
public void onLinkProcessingComplete(Uri uri) {
String path = uri.getPath();
if (path != null && path.startsWith("/products")) {
startActivity(new Intent(this, ProductsActivity.class));
} else if (path != null && path.startsWith("/cart")) {
startActivity(new Intent(this, CartActivity.class));
} else {
startActivity(new Intent(this, MainActivity.class));
}
finish();
}
Troubleshooting
Intent filter test fails (Step 1):
- Verify
android:exported="true"is set on your activity - Check that the activity is registered in AndroidManifest.xml
- Rebuild and reinstall the app
assetlinks.json not accessible (Step 2):
- Contact your Blueshift implementation team to verify CNAME setup
- Confirm you submitted the correct assetlinks.json content to Blueshift
Link opens in browser instead of app (Step 3):
- Android requires domain verification before routing links to apps
- Uninstall and reinstall the app to retry verification
- Verify assetlinks.json includes the correct package name and SHA-256 fingerprint
- Check that the CNAME domain correctly points to
links.getblueshift.com
Existing app links stopped working:
- Verify assetlinks.json includes entries for all required domains
- See Android's multi-subdomain guide
Reference documentation
Updated 8 days ago