Skip to content

Deep Links

Trace delivers deep links to your app in two scenarios:

  1. Direct deep links — user clicks a Trace short link while the app is installed. The link opens the app directly.
  2. Deferred deep links — user clicks a link, installs the app, then opens it for the first time. Trace delivers the deep link payload after attribution.

Both arrive through the same listener.

Trace.setDeepLinkListener { deepLink ->
val path = deepLink.path // "/product/123"
val params = deepLink.params // {"color": "blue"}
val deferred = deepLink.isDeferred // true if from install attribution
navigateTo(path, params)
}

Nav3 deep link support is split across two artifacts. Add both:

dependencies {
implementation("io.traceclick:trace-sdk:<version>") // core SDK + mapper
implementation("io.traceclick:trace-sdk-nav3:<version>") // Nav3 entry decorator
}

The deep link mapper lives in the core SDK (com.trace.sdk.compose.rememberDeepLinkMapper). The Nav3-specific decorator and navigation helpers live in the Nav3 artifact (com.trace.sdk.nav3.rememberTraceEntryDecorator, com.trace.sdk.nav3.navigateReplacing).

Use rememberDeepLinkMapper to convert deep link paths into typed route objects:

import com.trace.sdk.compose.rememberDeepLinkMapper
val mapper = rememberDeepLinkMapper {
route("/product/{id}") { params ->
ProductRoute(id = params.require("id"))
}
route("/invite/{code}") { params ->
InviteRoute(code = params.require("code"))
}
route("/settings") {
SettingsRoute
}
}

Pass the mapper to rememberTraceEntryDecorator from the Nav3 module:

import com.trace.sdk.nav3.rememberTraceEntryDecorator
import com.trace.sdk.nav3.navigateReplacing
TraceProvider {
val decorator = rememberTraceEntryDecorator(
backStack = backStack,
routeMapper = mapper
)
NavDisplay(
backStack = backStack,
entryDecorators = listOf(decorator)
)
}

When a deep link arrives with path /product/abc, the mapper calls your lambda and pushes ProductRoute(id = "abc") onto the back stack.

Not every deep link maps to a screen. Use action() for deep links that trigger a side-effect without navigation, such as claiming a reward, toggling a feature flag, or applying a promo code:

val mapper = rememberDeepLinkMapper {
// Navigation deep links
route("/product/{id}") { params ->
ProductRoute(id = params.require("id"))
}
// Action deep links — no navigation, just a side-effect
action("/claim/promo/{amount}") { params ->
rewardRepository.claim(params.require("amount"))
}
action("/feature/{flag}/enable") { params ->
featureFlags.enable(params.require("flag"))
}
}

Under the hood the mapper returns a sealed DeepLinkResult:

sealed interface DeepLinkResult<out T> {
data class Navigate<T>(val route: T) : DeepLinkResult<T>
data class Action(val execute: () -> Unit) : DeepLinkResult<Nothing>
}

route() mappings produce Navigate results that are pushed onto the Nav3 back stack. action() mappings produce Action results whose execute block is invoked immediately by the decorator without any navigation change.

Use {name} in the path pattern to capture segments:

route("/product/{id}") { params ->
// params.require("id") -> "abc"
}
route("/category/{cat}/item/{item}") { params ->
// params.require("cat") -> "electronics"
// params.require("item") -> "phone-123"
}

Query parameters from deepLinkParams are also available:

// Link created with: deepLinkPath="/product/123", deepLinkParams={"color": "blue"}
route("/product/{id}") { params ->
val id = params.require("id") // "123"
val color = params["color"] // "blue"
ProductRoute(id, color)
}
MethodReturn typeBehavior
params["key"]String?Returns null if missing
params.require("key")StringThrows if missing
params.int("key")Int?Parses to Int, null if missing or invalid
params.long("key")Long?Parses to Long
params.boolean("key")Boolean?Parses to Boolean

Deferred deep links often arrive before the user has signed in. Use an auth gate to hold the deep link until the user is authenticated:

import com.trace.sdk.nav3.rememberTraceEntryDecorator
val decorator = rememberTraceEntryDecorator(
backStack = backStack,
routeMapper = mapper,
authGate = { isLoggedIn } // deep link waits until this returns true
)

The flow:

  1. User clicks campaign link, installs app, opens app
  2. SDK receives deferred deep link for /invite/abc
  3. authGate returns false (user not logged in yet) — deep link is parked
  4. User signs up or logs in — authGate returns true
  5. Deep link is delivered — InviteRoute(code = "abc") pushed to back stack

When creating short links via the API, specify the deep link data:

Terminal window
curl -X POST https://api.traceclick.io/v1/links \
-H "X-Api-Key: tr_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"deepLinkPath": "/product/123",
"deepLinkParams": {"color": "blue", "ref": "email"},
"campaignId": "summer_sale",
"fallbackUrl": "https://yourapp.com/product/123"
}'

When this link drives an install, the SDK delivers:

path = "/product/123"
params = {"color": "blue", "ref": "email"}
isDeferred = true