The first screen a new user sees after installing your app is your highest-leverage onboarding moment. It sets expectations, establishes relevance, and determines whether the user continues past the first 60 seconds.
Most apps show every new user the same generic welcome screen, regardless of how they arrived. A user who clicked a referral from a friend, a user who tapped an ad for a specific product, and a user who found the app through a search all land in the same place. That is a missed opportunity.
Deferred deep linking changes this. By passing context through the install process, you can greet each new user with an experience that reflects why they downloaded the app in the first place.
What Context Can Travel Through an Install
A deferred deep link can carry anything you can express in a URL path and query parameters. Practical examples:
- Referral code: who invited this user
- Campaign source and content: which ad, email, or post drove the install
- Specific content: a product, article, or feature the user was viewing when they decided to install
- Promotion code: a discount or offer that was advertised
- Onboarding variant: an A/B test arm for the onboarding flow
- Pre-fill data: the user's email address if they started a signup flow on web before installing
All of this context is recorded at click time, stored on the server, and delivered to your app on first open via Tolinku's attribution mechanism. See how deferred deep linking works for a full explanation of the underlying mechanism.
Designing Context-Aware Onboarding
Before writing any code, map the contexts you want to handle. For each entry point that drives installs, define what the first-open experience should look like.
| Entry Point | Context Passed | First-Open Experience |
|---|---|---|
| Friend referral | referral_code, referrer_name |
"Sarah invited you. You both get 1 month free." |
| Product ad (specific item) | product_id, campaign |
Show the product page, skip category browsing |
| Email re-engagement | user_email, content_id |
Pre-fill email in sign-up, show content directly |
| App Store browse (organic) | None | Standard onboarding |
Keep the experience honest. If the user clicked an ad for a specific feature, take them there. Do not bait-and-switch with a feature that is not immediately available.

Implementation: Passing Context in the Link
When generating links that drive installs, include all the context you will need on the other side.
For a referral with a user name to display:
https://tolinku.link/ref/ABC123?referrer_name=Sarah&promo=FRIEND30
For a product-specific install from an ad:
https://tolinku.link/product/9876?campaign=spring_sale&variant=b
For an email-to-install flow where the user started on web:
https://tolinku.link/signup?email=jane%40example.com&source=email_campaign_march
Tolinku records all URL parameters at click time and returns them in the params dictionary on first open.
Retrieving Context on First Open
iOS (Swift)
import TolinkuSDK
class AppStartCoordinator {
func start(window: UIWindow) {
Tolinku.configure(publishableKey: "tolk_pub_your_key_here")
if !InstallState.hasResolvedFirstOpen {
resolveAndOnboard(window: window)
} else {
showHome(window: window)
}
}
private func resolveAndOnboard(window: UIWindow) {
Tolinku.shared.resolveDeferred { result in
DispatchQueue.main.async {
InstallState.hasResolvedFirstOpen = true
let context = OnboardingContext(from: result)
self.showOnboarding(window: window, context: context)
}
}
}
private func showOnboarding(window: UIWindow, context: OnboardingContext) {
let vc = OnboardingViewController(context: context)
window.rootViewController = UINavigationController(rootViewController: vc)
window.makeKeyAndVisible()
}
private func showHome(window: UIWindow) {
let vc = HomeViewController()
window.rootViewController = UINavigationController(rootViewController: vc)
window.makeKeyAndVisible()
}
}
struct OnboardingContext {
let referralCode: String?
let referrerName: String?
let productId: String?
let promoCode: String?
let prefillEmail: String?
let source: String?
let abVariant: String?
let isOrganic: Bool
init(from result: Result<TolinkuDeepLink, Error>) {
switch result {
case .success(let deepLink):
self.referralCode = deepLink.referralCode
self.referrerName = deepLink.params["referrer_name"]
self.productId = deepLink.params["product_id"]
self.promoCode = deepLink.params["promo"]
self.prefillEmail = deepLink.params["email"]
self.source = deepLink.source
self.abVariant = deepLink.params["variant"]
self.isOrganic = false
case .failure:
self.referralCode = nil
self.referrerName = nil
self.productId = nil
self.promoCode = nil
self.prefillEmail = nil
self.source = nil
self.abVariant = nil
self.isOrganic = true
}
}
}
Android (Kotlin)
data class OnboardingContext(
val referralCode: String?,
val referrerName: String?,
val productId: String?,
val promoCode: String?,
val prefillEmail: String?,
val source: String?,
val abVariant: String?,
val isOrganic: Boolean
) {
companion object {
fun fromDeepLink(deepLink: TolinkuDeepLink) = OnboardingContext(
referralCode = deepLink.referralCode,
referrerName = deepLink.params["referrer_name"],
productId = deepLink.params["product_id"],
promoCode = deepLink.params["promo"],
prefillEmail = deepLink.params["email"],
source = deepLink.source,
abVariant = deepLink.params["variant"],
isOrganic = false
)
fun organic() = OnboardingContext(
referralCode = null, referrerName = null, productId = null,
promoCode = null, prefillEmail = null, source = null,
abVariant = null, isOrganic = true
)
}
}
class LaunchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!InstallState.hasResolvedFirstOpen(this)) {
Tolinku.shared.resolveDeferred(this) { result ->
InstallState.markFirstOpenResolved(this)
val context = result.fold(
onSuccess = { OnboardingContext.fromDeepLink(it) },
onFailure = { OnboardingContext.organic() }
)
runOnUiThread { launchOnboarding(context) }
}
} else {
launchHome()
}
}
private fun launchOnboarding(context: OnboardingContext) {
val intent = Intent(this, OnboardingActivity::class.java).apply {
putExtra("onboarding_context", context)
}
startActivity(intent)
finish()
}
private fun launchHome() {
startActivity(Intent(this, HomeActivity::class.java))
finish()
}
}
Building the Custom Welcome Screen
With the OnboardingContext in hand, your first onboarding screen can adapt.
A referral welcome screen:
class OnboardingViewController: UIViewController {
var context: OnboardingContext
override func viewDidLoad() {
super.viewDidLoad()
if let name = context.referrerName {
titleLabel.text = "\(name) invited you to join."
subtitleLabel.text = "You both get 30 days free."
} else if let product = context.productId {
titleLabel.text = "You were looking at something."
subtitleLabel.text = "Sign up to get it."
loadProduct(id: product) // Show product image and name
} else {
titleLabel.text = "Welcome."
subtitleLabel.text = "Here's what you can do."
}
if let email = context.prefillEmail {
emailField.text = email
}
}
}
Keep welcome screens short. One headline, one subheadline, one call to action. The personalization should feel natural, not surveillance-y. "Sarah invited you" feels warm. "We know you clicked our Facebook ad on Tuesday" does not.

Pre-Filling Data
If the user started a sign-up flow on your website before installing, you may have their email address available through the deep link context. Pre-filling it removes friction and signals that you recognize them.
A web-to-app onboarding flow:
- User visits your website and enters their email in a "Get the App" form.
- Your server generates a Tolinku link:
https://tolinku.link/[email protected]&source=web_signup - The user taps the link (or scans a QR code), goes to the App Store, installs, and opens the app.
- Your app retrieves
emailfrom the deep link params and pre-fills the registration form.
Pre-filled data should always be editable. The user may have used a different email or may want to change the pre-filled value. Do not lock the field.
Onboarding A/B Testing with Deep Links
You can use the variant parameter to run onboarding A/B tests tied to acquisition campaigns. For a campaign where you want to test two onboarding flows:
- Control link:
https://tolinku.link/install?utm_campaign=march&variant=a - Variant link:
https://tolinku.link/install?utm_campaign=march&variant=b
Split traffic by serving different links in your ad creative or landing page. On first open, your app reads params["variant"] from the deep link and routes to the corresponding onboarding flow.
Track conversion by variant in your analytics system. Because the variant parameter travels through the install, it is available for your downstream metrics.
Measuring Onboarding Conversion
Attribution context flows into Tolinku's analytics with the deep link parameters intact. This lets you measure:
- Registration rate by source: did users from referrals complete sign-up more often than users from paid ads?
- Time to first key action by entry context: do users who saw a personalized welcome complete their first action faster?
- Drop-off by onboarding variant: which onboarding flow retains users through step 3?
- Promo code redemption rate: how many users who received a promo code through a deep link actually used it?
The analytics dashboard captures these breakdowns using the UTM parameters and custom params passed through the deep link. Set up conversion events in your Tolinku Appspace to track installs-to-registrations as a funnel.
For deeper product analytics (time-in-session, feature usage by cohort), pipe the Tolinku attribution data into your analytics platform (Mixpanel, Amplitude, PostHog) using the webhook integration.
Avoiding Common Mistakes
Showing personalization on returning opens. The first-open guard (hasResolvedFirstOpen) is critical. Without it, a user who reinstalls the app will see the new-user welcome screen again, which is confusing if they already have an account.
Over-promising in the welcome message. If you display a promo code in the welcome screen, make sure the code is actually applied. Route the user directly to a screen where they can redeem it. Do not make them hunt for the redemption field.
Not handling the null case. Some installs will be organic (no click to attribute). Your onboarding code must handle OnboardingContext.isOrganic == true gracefully. Test this path explicitly.
Assuming the email pre-fill is valid. The email in the deep link came from a URL parameter. It may be stale, misspelled, or belong to a different user if the link was forwarded. Validate it server-side when the user submits the registration form.
Related Resources
Get deep linking tips in your inbox
One email per week. No spam.