Complete API reference for the LinkForty Android SDK.
- LinkForty - Main SDK class
- LinkFortyConfig - Configuration
- DeepLinkData - Deep link data model
- CreateLinkOptions - Link creation options
- CreateLinkResult - Link creation result
- InstallResponse - Attribution response
- UTMParameters - UTM tracking parameters
- LinkFortyError - Error types
- Type Aliases - Callback types
- LinkFortyNavObserver - Automatic screen-view tracking
Main singleton class providing the SDK interface.
LinkForty.sharedAvailable before initialize(). Callbacks registered with onDeferredDeepLink / onDeepLink and links passed to handleDeepLink before then are kept, and the links are resolved once initialize() has configured the SDK. Methods that need a reported install (trackEvent, createLink, …) throw LinkFortyError.NotInitialized until initialize() succeeds.
Initializes the SDK with configuration and reports the install.
suspend fun initialize(
context: Context,
config: LinkFortyConfig,
attributionWindowHours: Int? = null,
deviceId: String? = null
): InstallResponseParameters:
context: Android Context (stored asapplicationContext)config: SDK configuration (required)attributionWindowHours: Attribution window in hours (default:config.attributionWindowHours)deviceId: Optional device identifier for attribution (e.g., GAID)
Returns: InstallResponse with attribution data
Throws: LinkFortyError if initialization fails. If the install can't be reported (for example, offline on the first launch), the SDK is left uninitialized and keeps registered callbacks: call initialize() again.
Example:
val config = LinkFortyConfig(
baseURL = "https://api.linkforty.com", // the hosted service; your own server's URL if self-hosted
appToken = "at_a1b2c3d4..." // required on the hosted service
)
val response = LinkForty.initialize(context, config)
Log.d("LinkForty", "Install ID: ${response.installId}")Handles a URI that opened the app (App Link or custom scheme). Pass every URI that opens the app; the SDK decides which are LinkForty links.
fun handleDeepLink(uri: Uri): BooleanParameters:
uri: The deep link URI to handle
Returns: true if the URI is looked up as a LinkForty link, false otherwise.
Which URIs are looked up:
- A web (
http/https) URI on one of the workspace's link domains, or on the host ofbaseURL, is resolved on the server andonDeepLinkreceives the link's data. - With an
appToken, the SDK fetches the workspace's link domains (custom domains and the workspace's link host) from the server and stores them on the device. Until it knows them, every web URI is looked up; with noappToken, it never learns them. - Any other URI, including a custom scheme (
myapp://product/42) or a web URI on another host, makes no request:onDeepLinkreceives the URI's own path and query parameters, with nolinkId. - A URI handed over before
initialize()is held. On a launch with no stored domain list, a held URI waits up to 3 seconds for the domain fetch, then is decided against what is known. For a held URI, the return value istruefor a web URI.
Example:
intent.data?.let { uri ->
LinkForty.shared.handleDeepLink(uri)
}Registers a callback for deferred deep links (install attribution).
fun onDeferredDeepLink(callback: DeferredDeepLinkCallback)Parameters:
callback: Lambda invoked with deep link data, or null for an organic install. It is called once, on the launch that reports the install; a callback registered afterinitialize()returns still receives it.
Example:
LinkForty.shared.onDeferredDeepLink { deepLinkData ->
if (deepLinkData != null) {
Log.d("LinkForty", "Attributed: ${deepLinkData.shortCode}")
} else {
Log.d("LinkForty", "Organic install")
}
}Registers a callback for direct deep links (when app opens from a link).
fun onDeepLink(callback: DeepLinkCallback)Parameters:
callback: Lambda invoked with URI and parsed deep link data
Example:
LinkForty.shared.onDeepLink { uri, deepLinkData ->
Log.d("LinkForty", "Opened from: $uri")
deepLinkData?.deepLinkPath?.let { navigateToPath(it) }
}Creates a short link programmatically.
suspend fun createLink(options: CreateLinkOptions): CreateLinkResultParameters:
options: Link creation options (see CreateLinkOptions)
Returns: CreateLinkResult with the shareable URL, short code, and link ID
Throws:
LinkFortyError.NotInitializedif SDK not initializedLinkFortyError.MissingApiKeyif no API key configured
Note: Requires an API key. Posts to POST /api/sdk/v1/links, which uses templateId when given and the most recent template otherwise, and attaches externalUserId (per call, or from setExternalUserId). A server without that endpoint (404) gets POST /api/links instead; deduplicated is then null.
Example:
val result = LinkForty.shared.createLink(
CreateLinkOptions(
deepLinkParameters = mapOf("route" to "VIDEO_VIEWER"),
title = "Check this out!",
utmParameters = UTMParameters(source = "app", campaign = "share")
)
)
Log.d("LinkForty", "Share: ${result.url}")Tracks a custom event.
suspend fun trackEvent(
name: String,
properties: Map<String, Any>? = null
)Parameters:
name: Event name (e.g., "purchase", "signup")properties: Optional event properties (must be JSON-serializable)
Throws: LinkFortyError for an empty name, when not initialized, or when the server rejects the event (a 4xx). An event that can't be sent now (no connection, or a 5xx) is queued and the call returns normally; see Offline Support.
Example:
LinkForty.shared.trackEvent("purchase", mapOf("product_id" to "123", "amount" to 29.99))Tracks a revenue event.
suspend fun trackRevenue(
amount: BigDecimal,
currency: String,
properties: Map<String, Any>? = null
)Parameters:
amount: Revenue amount (must be non-negative)currency: Currency code (e.g., "USD", "EUR")properties: Optional additional properties
Throws: LinkFortyError for an empty name, when not initialized, or when the server rejects the event (a 4xx). An event that can't be sent now (no connection, or a 5xx) is queued and the call returns normally; see Offline Support.
Example:
LinkForty.shared.trackRevenue(
amount = BigDecimal("29.99"),
currency = "USD",
properties = mapOf("product_id" to "123")
)Reports a screen view. Emits a screen_view event carrying the screen name and the previously tracked screen, stamped with the active last-click attribution context, so the dashboard can build a per-link screen-flow funnel.
suspend fun trackScreenView(
name: String,
properties: Map<String, Any>? = null
)Parameters:
name: Screen name (e.g., "ProductDetail"). Must not be blank.properties: Optional additional properties
Throws: as trackEvent does (including for a blank screen name)
Example:
LinkForty.shared.trackScreenView("ProductDetail")For automatic tracking with Jetpack Navigation, use LinkFortyNavObserver.
Sends the queued events now, oldest first. The SDK also sends them on its own; see Offline Support.
suspend fun flushEvents()Clears the event queue without sending events.
fun clearEventQueue()Returns the number of events currently queued.
val queuedEventCount: IntReturns the install ID if available, null if not initialized.
fun getInstallId(): String?Returns the install attribution data if available.
fun getInstallData(): DeepLinkData?Returns whether this is the first launch.
fun isFirstLaunch(): BooleanClears all stored SDK data: the install ID, install attribution, queued events and the last-click attribution context. The next launch reports a new install. Registered callbacks stay registered.
fun clearData()Resets the SDK to uninitialized state. Does NOT clear stored data — call clearData() first if needed.
fun reset()Configuration for the LinkForty SDK.
data class LinkFortyConfig(
val baseURL: String,
val apiKey: String? = null,
val appToken: String? = null,
val debug: Boolean = false,
val attributionWindowHours: Int = 168
)Parameters:
baseURL: The LinkForty server:https://api.linkforty.comon the hosted service, or your own server (must be HTTPS except localhost/127.0.0.1/0.0.0.0/10.0.2.2)apiKey: API key, needed only forcreateLink(). It is a secret: leave it out unless the app creates linksappToken: Public workspace token. Required on the hosted service: it limits install matching to your workspace's links and puts organic installs in your workspace; a self-hosted Core server doesn't need it. It is sent with every request asX-LinkForty-App-Token, so the server answers with your workspace's links only, and lets the SDK learn your link domains (seehandleDeepLink). Find it in the dashboard under Workspace Settings → App Token. Format:at_<32 hex chars>. Safe to ship in your app bundle.debug: Enable debug logging (default: false)attributionWindowHours: Attribution window in hours (default: 168 = 7 days, max: 2160 = 90 days)
Validates the configuration. Throws LinkFortyError.InvalidConfiguration if validation fails.
fun validate()Deep link data model containing parsed link information.
val shortCode: String // Link short code (required)
val iosURL: String? // iOS deep link URL
val androidURL: String? // Android deep link URL
val webURL: String? // Fallback web URL
val utmParameters: UTMParameters? // UTM tracking parameters
val customParameters: Map<String, String>? // Custom query parameters
val deepLinkPath: String? // Deep link path for in-app routing
val appScheme: String? // App URI scheme
val clickedAt: String? // ISO 8601 click timestamp
val linkId: String? // Link UUID from the backend
val confidenceScore: Double? // Match confidence (0-100); deferred deep links only
val matchedFactors: List<String>? // Matched fingerprint factors; deferred deep links only
val attributionMethod: String? // "install_referrer" or "fingerprint"; deferred deep links only
val competingLinks: Int? // Other links in contention for the install; deferred deep links onlyThe last four are set on the deferred deep link (onDeferredDeepLink, getInstallData()) and are null on a direct open. See competingLinks under InstallResponse.
Parses clickedAt as a java.time.Instant.
fun clickedAtDate(): Instant?Response from install attribution API.
val installId: String // Unique install ID
val attributed: Boolean // Whether install was attributed
val confidenceScore: Double // Confidence score (0-100)
val matchedFactors: List<String> // Matched fingerprint factors
val deepLinkData: DeepLinkData? // Deep link data if attributed
val attributionMethod: String? // "install_referrer" (Play install referrer) or "fingerprint" when matched to a click, "none" when organic
val competingLinks: Int? // Other links in contention for the installattributionMethod and competingLinks are null when the server does not send them, and on launches after the first, when initialize() returns the attribution stored on the device.
How many other links had a click from the install's network that also reached the confidence threshold. 0 means nothing else was in contention (and is always 0 for an organic install). Above 0, a tap on another device on the same network may be what led to this install, so an app acting on a sensitive link, such as a sign-in or a gift, can treat the match as unverified and, for example, ask the user to confirm.
Options for creating a short link programmatically.
data class CreateLinkOptions(
val templateId: String? = null,
val templateSlug: String? = null,
val deepLinkParameters: Map<String, String>? = null,
val title: String? = null,
val description: String? = null,
val customCode: String? = null,
val utmParameters: UTMParameters? = null
)Result of creating a short link.
val url: String // Full shareable URL
val shortCode: String // Generated short code
val linkId: String // Link UUIDUTM parameters for campaign tracking.
val source: String? // Campaign source
val medium: String? // Campaign medium
val campaign: String? // Campaign name
val term: String? // Campaign term
val content: String? // Campaign contentval hasAnyParameter: Boolean // True if any parameter is setSealed class of errors thrown by the SDK.
class NotInitialized : LinkFortyError
class AlreadyInitialized : LinkFortyError
class InvalidConfiguration(detail: String) : LinkFortyError
class NetworkError(cause: Throwable) : LinkFortyError
class InvalidResponse(statusCode: Int?, responseMessage: String?) : LinkFortyError
class DecodingError(cause: Throwable) : LinkFortyError
class EncodingError(cause: Throwable) : LinkFortyError
class InvalidEventData(detail: String) : LinkFortyError
class InvalidDeepLinkUrl(detail: String) : LinkFortyError
class MissingApiKey : LinkFortyErrortry {
LinkForty.shared.trackEvent("test")
} catch (e: LinkFortyError) {
when (e) {
is LinkFortyError.NotInitialized -> Log.e("LinkForty", "Not initialized")
is LinkFortyError.NetworkError -> Log.e("LinkForty", "Network error", e.cause)
is LinkFortyError.InvalidEventData -> Log.e("LinkForty", "Invalid event: ${e.message}")
else -> Log.e("LinkForty", "Error: ${e.message}")
}
}typealias DeferredDeepLinkCallback = (DeepLinkData?) -> Unittypealias DeepLinkCallback = (Uri, DeepLinkData?) -> UnitA NavController.OnDestinationChangedListener that automatically reports a screen_view (see trackScreenView) whenever the Jetpack Navigation destination changes.
Requires androidx.navigation, which your app already provides if it uses Jetpack Navigation. The SDK depends on it only as compileOnly, so apps that don't use Navigation are unaffected.
class LinkFortyNavObserver(
screenNameExtractor: (NavDestination) -> String? = ::defaultScreenName,
scope: CoroutineScope = CoroutineScope(Dispatchers.Main)
) : NavController.OnDestinationChangedListenerBy default a destination's route (Compose navigation) or label (XML nav graph) is used as the screen name; destinations with neither are skipped. Pass screenNameExtractor to customize.
Example:
import com.linkforty.sdk.navigation.LinkFortyNavObserver
navController.addOnDestinationChangedListener(LinkFortyNavObserver())All SDK methods are thread-safe. Callbacks are executed on the main thread. Concurrent initialize() calls are serialized: one reports the install, the others throw LinkFortyError.AlreadyInitialized.
The SDK uses Kotlin coroutines for all asynchronous operations:
lifecycleScope.launch {
val response = LinkForty.initialize(context, config)
LinkForty.shared.trackEvent("test")
LinkForty.shared.flushEvents()
}Every tracked event is stored on the device before it is sent and removed once the server has it, so events survive the app being closed. An event that can't be sent (no connection, or a 5xx response) stays queued, up to 100 events, and trackEvent returns normally. Queued events are sent, oldest first:
- when
initialize()completes on the next launch, - when the app returns to the foreground,
- after the next event that sends, and
- when you call
flushEvents().
Each event carries a clientEventId, generated when it is tracked and kept through every retry, so the server stores an event it receives twice only once. An event the server refuses with a 4xx is not queued, and trackEvent throws.
The SDK doesn't watch the network connection and needs no permission beyond INTERNET.
For more information, see the full documentation or LinkForty Docs.