Deferred Deep Linking
Deferred deep linking allows you to route new users to specific content after they install your app, creating a seamless experience from click to conversion.
What is Deferred Deep Linking?
Regular deep linking only works for users who already have your app installed.
Deferred deep linking works for new users who don't have your app yet:
- User clicks link to product page
- User doesn't have app → Sent to App Store/Google Play
- User installs app
- User opens app for first time
- App automatically navigates to the original product page
The "deep link" is deferred until after install.
Why It Matters
Without Deferred Deep Linking
User clicks: Instagram ad for wireless headphones
Flow:
- Click ad → App Store
- Install app
- Open app → Generic home screen
- User has to search for headphones again
- 60-70% drop-off - user forgets or gives up
With Deferred Deep Linking
User clicks: Same Instagram ad
Flow:
- Click ad → App Store
- Install app
- Open app → Automatically shows wireless headphones
- User adds to cart immediately
- 15-25% drop-off - seamless experience
Impact: 3-5x higher conversion rate from click to purchase.
How It Works
1. Link Creation
Create a link with deep link parameters:
curl -X POST https://api.linkforty.com/api/links \
-H "Authorization: Bearer $LINKFORTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "template_123",
"originalUrl": "https://example.com/products/123?productId=123&category=electronics",
"iosUrl": "https://apps.apple.com/app/id123456789",
"androidUrl": "https://play.google.com/store/apps/details?id=com.yourapp",
"attributionWindowHours": 168
}'
Generated: https://lnk.forty.com/abc123
One link can serve many destinations: append query parameters when you share it and they reach the app after install, so you do not need a separate link per item. See URL Parameter Passthrough.
2. User Clicks Link
When user clicks, LinkForty:
- Detects device (iOS, Android, or Web)
- Creates fingerprint from:
- IP address
- User-agent
- Screen resolution
- Timezone
- Language
- Stores fingerprint with link data and timestamp
- Redirects to App Store (iOS) or Google Play (Android)
3. User Installs App
User downloads and installs from store.
Important: Attribution window starts when user clicks link, not when they install.
4. User Opens App
On first launch, the SDK reports the install and invokes your deferred deep link handler:
import LinkForty from '@linkforty/mobile-sdk-react-native';
LinkForty.onDeferredDeepLink((deepLinkData) => {
// deepLinkData is the attributed link's data, or null for organic installs
});
5. LinkForty Matches Install
SDK:
- Generates fingerprint from same device signals
- Sends to LinkForty API
- Searches for matching click within attribution window
- Returns deep link data if match found
// Returns:
{
productId: "123",
category: "electronics",
utm_source: "instagram",
utm_campaign: "spring-sale"
}
6. App Navigates
Your app uses deep link data to navigate:
if (deepLinkData && deepLinkData.productId) {
navigation.navigate('Product', { id: deepLinkData.productId });
}
User sees the original product page they clicked on!
Fingerprint Matching
What is Fingerprinting?
Fingerprinting creates a unique identifier from device characteristics without requiring:
- Device IDs (IDFA, GAID)
- User accounts
- Cookies
- App Tracking Transparency permission (iOS)
Fingerprint Components
LinkForty uses these signals:
| Signal | Example | Stability |
|---|---|---|
| IP Address | 192.168.1.1 | High |
| User-Agent | iPhone14,2; iOS 17.1 | Very High |
| Screen Resolution | 1170x2532 | Very High |
| Timezone | America/New_York | High |
| Language | en-US | High |
| Platform | iOS | Very High |
Combined, these create a fingerprint like:
fp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Match Accuracy
Expected accuracy: 70-80%
| Scenario | Match Rate |
|---|---|
| Same WiFi, same device, under 24h | 90-95% |
| Same cellular, same device, under 7 days | 75-85% |
| Different network (WiFi → cellular) | 60-70% |
| VPN or proxy in between | 30-50% |
| Over 14 days between click and install | 50-60% |
Why Matches Fail
Common reasons for no match:
- Network changed - Clicked on WiFi, installed on cellular
- VPN/proxy used - IP address changes
- Attribution window expired - Installed too long after click
- Different browser - In-app browser vs Safari (iOS)
- iOS Private Relay - Hides IP address
- Multiple devices - Clicked on iPad, installed on iPhone
Improving Match Rate
1. Longer attribution windows
- 7 days: 70-75% match rate
- 14 days: 75-80% match rate
- 30 days: 65-70% match rate (more false positives)
2. Platform-specific links
- Create separate links for iOS and Android
- Reduces ambiguity in matching
3. Direct traffic
- Social media → Good match rates (in-app browsers)
- SMS/iMessage → Excellent match rates (same device)
- Email → Variable (different devices)
Implementation
Step 1: Create Links
Add deep link parameters to your link URLs:
# Product link
curl -X POST https://api.linkforty.com/api/links \
-H "Authorization: Bearer $LINKFORTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "template_123",
"originalUrl": "https://example.com/products/headphones?productId=456&category=audio",
"iosUrl": "https://apps.apple.com/app/id123",
"androidUrl": "https://play.google.com/store/apps/details?id=com.app"
}'
Deep link parameters: Any query parameter in originalUrl becomes available in app.
Step 2: Integrate SDK
Install and initialize the SDK:
import LinkForty from '@linkforty/mobile-sdk-react-native';
// Initialize on app launch
LinkForty.init({
baseUrl: 'https://go.yourdomain.com',
appToken: 'at_your_app_token', // recommended for Cloud
});
See SDK Integration Guide for full setup.
Step 3: Check for Deep Link
On first app launch:
import { useEffect } from 'react';
import LinkForty from '@linkforty/mobile-sdk-react-native';
import { useNavigation } from '@react-navigation/native';
function App() {
const navigation = useNavigation();
useEffect(() => {
// Fires on a new install attributed to a link; null for organic installs
LinkForty.onDeferredDeepLink((data) => {
if (data) {
console.log('Deep link data:', data);
handleDeepLink(data);
}
});
}, []);
const handleDeepLink = (data) => {
const params = data.customParameters ?? {};
if (params.productId) {
navigation.navigate('Product', { id: params.productId });
} else if (params.category) {
navigation.navigate('Category', { slug: params.category });
} else if (data.deepLinkPath) {
// Route using the link's deep link path
navigation.navigate(data.deepLinkPath);
}
};
return (
// Your app
);
}
Step 4: Test
Test flow:
- Create test link with query parameters
- Copy link to notes app on test device
- Uninstall app from device
- Click link from notes
- Install app from store
- Open app
- Check if navigated to correct content
Expected logs:
[LinkForty] Checking for deep link...
[LinkForty] Match found!
[LinkForty] Deep link data: {
productId: "456",
category: "audio",
utm_source: "test"
}
[App] Navigating to product 456
URL Parameter Passthrough
You do not need a separate link for every piece of content. Add query parameters to one link when you share it, and they reach the app after the install.
https://go.example.com/content?slug=titanic
https://go.example.com/content?slug=spiderman-homecoming
Both are the same link. The value you appended arrives in customParameters, whether the app was just installed or was already on the device:
function openContent(data) {
const slug = data?.customParameters?.slug;
if (slug) {
navigation.navigate('Content', { slug });
}
}
// New install, matched back to the click
LinkForty.onDeferredDeepLink(openContent);
// App already installed — the OS hands the link straight to the app
LinkForty.onDeepLink((url, data) => openContent(data));
The two entry points are different moments, but the value is read the same way in both. You do not need to parse the URL yourself.
On a direct open the SDK merges the parameters from the tapped URL into the payload it resolves for the link. If a name appears in both, the URL wins — see Precedence.
SDK Versions
Delivering URL parameters on a direct open requires:
| SDK | Minimum version |
|---|---|
| React Native | 1.6.1 |
| Expo | 1.6.1 |
| iOS | 1.4.1 |
| Android | 1.3.1 |
| Flutter | 0.2.1 |
Earlier versions still receive parameters on the deferred path. On a direct open they fall back to the link's stored configuration, so customParameters will not carry what was appended to the URL.
Reserved Parameters
Some names are consumed by LinkForty and never reach your app, because they already do a job. Matching is case-insensitive, and the first two are matched by prefix — every name starting with utm_ or fp_ is reserved, not just the ones listed here:
| Name | Used for |
|---|---|
utm_* — e.g. utm_source, utm_medium, utm_campaign, utm_term, utm_content | campaign reporting — see Campaigns |
fp_* — e.g. fp_tz, fp_lang, fp_sw, fp_sh, fp_platform, fp_pv | the device fingerprint used to match the install |
lf_click (exact) | the click id LinkForty adds to your destination URL |
Anything else is passed through. Avoid the utm_ and fp_ prefixes for your own values.
Limits
- 16 parameters per link. Beyond that, the extras are dropped.
- 256 characters per value. Longer values are silently truncated, not rejected — the link still works and the click is still recorded, so check the length yourself if a value might be long.
Precedence
A parameter on the URL beats one configured on the link itself. A link set up with slug=default that is shared as ?slug=titanic delivers titanic — what the sharer put on the URL is more specific than the link's stored configuration.
What Does Not Come Through
- Bot traffic. Clicks classified as bots record no parameters at all.
- Values added after the click. Parameters are captured at click time. Changing the URL later does not change what an earlier click stored.
- The fragment. Anything after
#never reaches the server.
These values come from a public URL and anyone can set them to anything. Treat them as untrusted input: check that a value is one you expect before routing on it, and never pass one straight into a URL loader, a database query, or a privileged navigation.
Use Cases
E-commerce Product Links
Link: Instagram ad for wireless headphones
{
"originalUrl": "https://shop.example.com/products/wireless-headphones?productId=789",
"iosUrl": "https://apps.apple.com/app/id123",
"androidUrl": "https://play.google.com/store/apps/details?id=com.shop"
}
App flow:
const data = await LinkForty.getInstallData();
// { productId: "789" }
navigation.navigate('Product', { id: data.productId });
User sees: Wireless headphones product page immediately after install.
Content Deep Links
Link: Email with article link
{
"originalUrl": "https://blog.example.com/articles/how-to-cook-pasta?articleId=123§ion=recipes",
"iosUrl": "https://apps.apple.com/app/id456",
"androidUrl": "https://play.google.com/store/apps/details?id=com.blog"
}
App flow:
const data = await LinkForty.getInstallData();
// { articleId: "123", section: "recipes" }
navigation.navigate('Article', { id: data.articleId });
User sees: Article about cooking pasta.
Referral Programs
Link: Friend's referral link
{
"originalUrl": "https://example.com/referral/john-doe?referrerId=user_456&reward=10",
"iosUrl": "https://apps.apple.com/app/id789",
"androidUrl": "https://play.google.com/store/apps/details?id=com.app"
}
App flow:
const data = await LinkForty.getInstallData();
// { referrerId: "user_456", reward: "10" }
// Award referral credit
await awardReferralCredit(data.referrerId, data.reward);
// Show success message
showToast(`You received $${data.reward} credit from ${getReferrerName(data.referrerId)}!`);
User sees: Welcome message with referral credit.
Promotional Campaigns
Link: Limited-time offer
{
"originalUrl": "https://example.com/promo/spring-sale?couponCode=SPRING20&discount=20",
"iosUrl": "https://apps.apple.com/app/id123",
"androidUrl": "https://play.google.com/store/apps/details?id=com.app"
}
App flow:
const data = await LinkForty.getInstallData();
// { couponCode: "SPRING20", discount: "20" }
// Auto-apply coupon
await applyCoupon(data.couponCode);
// Navigate to sale
navigation.navigate('Sale', { discount: data.discount });
User sees: Sale page with coupon already applied.
Advanced Techniques
Multi-Step Onboarding
Defer deep link until after onboarding:
const [onboardingComplete, setOnboardingComplete] = useState(false);
const [pendingDeepLink, setPendingDeepLink] = useState(null);
useEffect(() => {
checkDeepLink();
}, []);
const checkDeepLink = async () => {
const data = await LinkForty.getInstallData();
if (data) {
setPendingDeepLink(data);
}
};
const handleOnboardingComplete = () => {
setOnboardingComplete(true);
if (pendingDeepLink) {
handleDeepLink(pendingDeepLink);
}
};
Conditional Navigation
Navigate based on user state:
const handleDeepLink = async (data) => {
const user = await getCurrentUser();
if (!user) {
// Not logged in - show login, then navigate
navigation.navigate('Login', {
redirectTo: 'Product',
redirectParams: { id: data.productId }
});
} else {
// Logged in - navigate directly
navigation.navigate('Product', { id: data.productId });
}
};
Deep Link Fallbacks
Handle missing or invalid data:
const handleDeepLink = async (data) => {
if (data.productId) {
// Validate product exists
const product = await api.getProduct(data.productId);
if (product) {
navigation.navigate('Product', { id: data.productId });
} else {
// Invalid product - fallback to category
if (data.category) {
navigation.navigate('Category', { slug: data.category });
} else {
// No valid destination - go to home
navigation.navigate('Home');
}
}
}
};
Deep Link Analytics
Track deep link usage:
const handleDeepLink = async (data) => {
// Track deep link event
await LinkForty.trackEvent({
eventName: 'deep_link_opened',
properties: {
productId: data.productId,
utm_source: data.utm_source,
utm_campaign: data.utm_campaign,
hasAccount: !!user
}
});
// Navigate
navigation.navigate('Product', { id: data.productId });
};
Troubleshooting
Deep Link Data Returns Null
Symptom: the deferred deep link handler fires with null (or getInstallData() returns null) despite the user clicking the link.
Debug steps:
-
Check attribution window:
# View link details
curl https://api.linkforty.com/api/links/abc123 \
-H "Authorization: Bearer $LINKFORTY_API_KEY"
# Check attribution_window_hours value -
Enable debug logging:
LinkForty.init({
apiKey: API_KEY,
debug: true
}); -
Check click was recorded:
- Go to Analytics → Links → Select link
- Verify click appears in list
- Note timestamp
-
Check install timing:
- Compare click timestamp to install time
- If >attribution window → No match expected
-
Verify network conditions:
- Same WiFi/cellular for click and install?
- VPN enabled?
- iOS Private Relay active?
Low Match Rates
Expected: 70-80% for normal conditions
If lower (e.g., 40-50%):
-
Increase attribution window:
- Try 14 days instead of 7
- May capture more delayed installs
-
Check traffic source:
- In-app browsers → Good (70-80%)
- Web browsers → Lower (60-70%)
- Email links → Variable (different devices)
-
Platform-specific issues:
- iOS 14+ privacy features reduce accuracy
- Android: Check Google Play Install Referrer integration
-
Geographic factors:
- Users in regions with unstable IPs
- Frequent VPN usage
Incorrect Content Loaded
Symptom: Deep link data has wrong product ID.
Possible causes:
-
False positive match:
- User clicked different link before
- Fingerprints happened to match
- Solution: Shorter attribution window
-
Query parameter typo:
- Check link URL has correct parameters
- Verify parameter names match app code
-
Cached data:
- Old deep link data cached
- Solution: Clear app data and retest
Best Practices
1. Always Provide Deep Link Parameters
Include useful data in every link:
Good:
originalUrl: "https://example.com/product?productId=123&category=electronics"
Bad:
originalUrl: "https://example.com/product"
2. Use Meaningful Parameter Names
Be explicit:
Good:
?productId=123&categorySlug=electronics
Bad:
?id=123&cat=elec
3. Handle Missing Parameters
Always provide fallbacks:
const data = await LinkForty.getInstallData();
if (data?.productId) {
navigation.navigate('Product', { id: data.productId });
} else if (data?.category) {
navigation.navigate('Category', { slug: data.category });
} else {
navigation.navigate('Home');
}
4. Test on Real Devices
Fingerprint matching doesn't work in simulators. Always test on physical devices.
5. Set Appropriate Attribution Windows
Match window to expected user behavior:
- Impulse products: 1-3 days
- Considered purchases: 7-14 days
- Referrals: 30-90 days
6. Track Deep Link Performance
Monitor conversion rates:
// Track when deep link opens
await LinkForty.trackEvent({
eventName: 'deep_link_opened',
properties: { productId: data.productId }
});
// Track if user converts
await LinkForty.trackEvent({
eventName: 'purchase',
value: 29.99,
properties: { productId: data.productId }
});
View in dashboard: See conversion rate from deep link open → purchase.
Next Steps
- SDK Integration - Set up mobile SDK
- React Native SDK - Complete React Native guide
- Attribution Windows - Configure windows
- Creating Links - Create deep links
- Analytics - Monitor performance
Further Reading
- Attribution Windows - Attribution concepts
- QR Codes - Offline-to-online deep linking