Deep Linking

Direct, deferred, and contextual links — what each one carries, how the SDK delivers it after install, and when to reach for which.

A deep link points at a specific place inside your app rather than the home screen. On iOS the OS mechanism is Universal Links; on Android it's App Links. LinkTrail supports three flavors on top of them, and most apps use all three.

TypeUser has app?What happens
DirectYesOS opens the app straight to the path.
DeferredNoUser installs first; the path is delivered on first open.
ContextualEitherPath plus custom data personalizes the first screen.

The install gap

The browser can't hand parameters to a newly installed app — the stores sit in between and strip context. Without a deferred deep link, every install from a 'see this product' link lands on your home screen and the intent is lost. LinkTrail closes that gap: on first launch the SDK matches the install to the click and delivers the same link.

Every SDK delivers the same link shape to onLink (field casing varies slightly per language):

FieldWhat it is
pathThe in-app destination to route to — falls back to / when none is set. Route on this.
customDataString key/value payload configured on the link (voucher codes, referrer IDs, …). A nested object or array arrives as a compact JSON string — decode it yourself.
campaign / channel / utmCampaign metadata for your own analytics
slug / urlThe link's short slug and canonical URL
iosUrl / androidUrl / fallbackUrlThe routing destinations configured on the link
source (2nd argument)deferred (first launch after install) or reengagement (app was installed)

Since 0.1.0 a deferred link is delivered at least once, on every platform. The SDK saves it on the device as soon as the install resolves it, and hands it to onLink once a handler is registered — in that launch or a later one. So a link survives a splash or login screen that registers onLink late, or an app killed before it got there.

  • Make the handler safe to run twice. The link is marked routed only after the handler has been called, so a crash inside it can mean one redelivery on the next launch. Once it is marked routed, a relaunch never routes it again.
  • It expires after 7 days. A saved link that no handler took within 7 days is dropped.
  • The install is never re-sent to deliver it, and resetForTesting clears the saved link.
  • Consent still decides when it resolves. While the decision is unset the install is held, so there is no link yet; either answer releases the install and the link routes. See Consent & privacy.

handleDeepLink returns true only for a LinkTrail link, which needs all three:

  1. 1The scheme is https. http is accepted only in debug builds (iOS DEBUG, a debuggable Android app).
  2. 2The host is linktrail.io or a subdomain of it, or a linkDomains entry or a subdomain of it. Case, the port and a trailing dot are ignored, and internationalised names are compared in punycode, so a look-alike host never matches.
  3. 3The path has a slug — https://yourapp.linktrail.io/ alone is not a link.

A URL whose authority contains \, %, whitespace or a control character, has a bad port, or whose host isn't a valid host name is never claimed, so host-confusion URLs such as https://linktrail.io@evil.com/x are rejected. Anything else (mailto:, your own myapp://settings, an OAuth callback) returns false — the async forms throw notALinkTrailURL / NotALinkTrailUrl and React Native resolves null — with no request and no onError, and logs [LinkTrail] [DEBUG] Not a LinkTrail link — ignoring: <url>.

linkDomains entries are normalised at configure: scheme, path and port are removed, the host is lowercased and a trailing dot dropped, so https://Go.Acme.com:443/x becomes go.acme.com. An invalid entry (blank, a wildcard, an empty label, a space) is ignored with a WARNING; a single-label entry such as com is kept with a WARNING, because it matches every host under it.