Mobile Attribution Without IDFA - Complete Guide (2025)
In April 2021, Apple released iOS 14.5 with App Tracking Transparency (ATT), requiring apps to ask users for permission before accessing their IDFA (Identifier for Advertisers). The result: ~70% of users decline permission, effectively ending IDFA-based attribution for most iOS users.
This guide explains how to track mobile app installs and attribution in the post-ATT era without relying on IDFA.
What Changed with iOS 14.5?
Before iOS 14.5 (Pre-April 2021)
IDFA was accessible without user permission:
// Old way (pre-iOS 14.5)
import AdSupport
let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString
// Returns: "8C9B7F7E-4B3C-4F9D-9E8A-1234567890AB"
Attribution platforms used IDFA:
- User clicks ad → IDFA recorded (
8C9B7F7E...) - User installs app → IDFA read (
8C9B7F7E...) - Match! → Install attributed to ad
Accuracy: 95-100% (perfect matching)
After iOS 14.5 (April 2021 - Present)
ATT Framework requires permission prompt:
// New way (iOS 14.5+)
import AppTrackingTransparency
ATTrackingManager.requestTrackingAuthorization { status in
switch status {
case .authorized:
// ~30% of users - IDFA accessible
let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString
case .denied:
// ~70% of users - IDFA returns zeros
let idfa = "00000000-0000-0000-0000-000000000000"
default:
break
}
}
The prompt users see:
"YourApp would like to track you across apps and websites owned by other companies"
[Ask App Not to Track] [Allow]
Reality: ~70% of users tap "Ask App Not to Track"
Impact on Mobile Attribution
Before iOS 14.5:
- IDFA-based attribution: 95-100% accuracy
- No user permission needed
- Standard across industry
After iOS 14.5:
- IDFA-based attribution: ~30% coverage (only users who grant permission)
- 70% of users cannot be tracked with IDFA
- Industry forced to adopt new methods
Result: The mobile attribution industry had to completely reinvent itself.
Attribution Methods Post-ATT
Method 1: Fingerprint Attribution (Privacy-Friendly)
How it works: Match users based on device characteristics instead of IDFA.
Fingerprint components:
{
"ip": "192.168.1.100",
"user_agent": "iPhone 15 Pro, iOS 17.2",
"screen_width": 1179,
"screen_height": 2556,
"screen_density": 3,
"language": "en-US",
"timezone": "America/Los_Angeles",
"carrier": "Verizon",
"os_version": "17.2",
"device_model": "iPhone15,2"
}
Matching process:
1. Click event: Record fingerprint
{ip: "192.168.1.100", screen: "1179x2556", ...}
2. Install event: Record fingerprint
{ip: "192.168.1.100", screen: "1179x2556", ...}
3. Compare fingerprints:
✓ IP matches
✓ Screen size matches
✓ Language matches
✓ Timezone matches
✓ OS version matches
→ Confidence: 85%
4. Attribution: Install attributed to click
Accuracy:
- ✅ 70-80% in ideal conditions
- ✅ 60-70% with network changes
- ✅ 50-60% on shared WiFi networks
Pros:
- ✅ Works without user permission (no ATT prompt)
- ✅ Privacy-friendly (no persistent IDs)
- ✅ GDPR compliant (fingerprints aren't personal data)
- ✅ Works for 100% of users (not just 30%)
Cons:
- ❌ Lower accuracy than IDFA (70-80% vs 95-100%)
- ❌ Can have false positives on shared IPs
- ❌ Accuracy decreases if IP changes between click and install
Best for: Privacy-conscious companies, general user acquisition
This is what LinkForty uses.
Method 2: SKAdNetwork (Apple's Privacy-Safe Solution)
How it works: Apple's official attribution API that provides aggregated, anonymized attribution data.
Flow:
1. User clicks ad → Ad network registers click with SKAdNetwork
2. User installs app → App notifies SKAdNetwork
3. 24-72 hours later → Apple sends postback to ad network
4. Ad network receives: "Campaign X drove an install" (anonymized)
What you get:
{
"campaign_id": "1234",
"conversion_value": "5", // You define what this means
"did_win": true,
"transaction_id": "abc123"
}
What you DON'T get:
- ❌ No user-level data (anonymized)
- ❌ No immediate attribution (24-72 hour delay)
- ❌ No device information
- ❌ Limited conversion data (6 bits only)
Accuracy:
- ✅ 100% accurate (Apple controls everything)
- ❌ But anonymized (can't track individual users)
Pros:
- ✅ Apple-approved (no permission needed)
- ✅ 100% accurate (Apple-controlled)
- ✅ Privacy-preserving
Cons:
- ❌ 24-72 hour delay (not real-time)
- ❌ Anonymized (can't track user journey)
- ❌ Complex setup
- ❌ Limited data (6-bit conversion value)
- ❌ Requires ad network integration
Best for: Paid advertising attribution (Facebook Ads, Google Ads)
Method 3: Probabilistic Attribution + Deterministic Fallback
How it works: Combine fingerprinting (probabilistic) with deterministic methods when available.
Strategy:
1. Try deterministic methods first:
- Universal Links (if user has app installed)
- Server-to-server attribution (if user logs in)
- IDFA (if user grants permission - ~30%)
2. Fall back to fingerprinting:
- If deterministic methods unavailable
- Fingerprint matching as backup
Example flow:
User clicks ad from Instagram
IF user has app installed:
→ Universal Link opens app directly (100% attribution)
ELSE IF user grants IDFA permission after install:
→ IDFA-based attribution (100% attribution)
ELSE:
→ Fingerprint attribution (70-80% attribution)
Best for: Maximum accuracy across all scenarios
This is the industry standard approach in 2025.
Implementing Attribution Without IDFA
Option 1: Using LinkForty (Recommended)
LinkForty uses fingerprint attribution (70-80% accuracy) with no user permission required.
1. Install SDK:
React Native:
npm install @linkforty/react-native-sdk
iOS Native:
pod 'LinkFortySDK' # Coming soon
Android Native:
implementation 'com.linkforty:sdk:1.0.0' # Coming soon
2. Initialize:
// React Native
import LinkForty from '@linkforty/react-native-sdk';
await LinkForty.init({
baseUrl: 'https://api.linkforty.com',
apiKey: 'your-api-key',
debug: __DEV__,
});
// iOS
import LinkFortySDK
LinkFortySDK.initialize(
baseUrl: "https://api.linkforty.com",
apiKey: "your-api-key",
debug: true
)
// Android
import com.linkforty.sdk.LinkFortySDK
LinkFortySDK.initialize(
context = this,
baseUrl = "https://api.linkforty.com",
apiKey = "your-api-key",
debug = true
)
3. Check attribution on app launch:
// React Native
const attribution = await LinkForty.getAttributionData();
if (attribution && attribution.matched) {
console.log('Install attributed to:', {
campaign: attribution.utmCampaign,
source: attribution.utmSource,
medium: attribution.utmMedium,
});
// Route to content
navigation.navigate(attribution.destination);
// Track attributed install
analytics.track('attributed_install', {
campaign: attribution.utmCampaign,
source: attribution.utmSource,
});
}
4. Track events (optional):
// Track purchase
await LinkForty.trackEvent('purchase', {
amount: 49.99,
currency: 'USD',
productId: 'premium-plan',
});
// Track subscription
await LinkForty.trackEvent('subscription_started', {
plan: 'pro',
price: 29.99,
});
5. View attribution in dashboard:
LinkForty Dashboard → Analytics
See:
- Click-to-install attribution
- Campaign performance
- Conversion rates
- Geographic breakdown
- Device breakdown
All without requiring IDFA permission!
Option 2: Build Your Own Fingerprinting
If you prefer to build in-house:
1. Collect fingerprint at click time:
// Click tracking server
app.get('/:shortCode', async (req, res) => {
const fingerprint = {
ip: req.ip,
userAgent: req.get('user-agent'),
language: req.get('accept-language'),
// Additional data from user-agent parsing:
device: parseDevice(req.get('user-agent')),
os: parseOS(req.get('user-agent')),
browser: parseBrowser(req.get('user-agent')),
};
// Store fingerprint with click event
await db.clicks.create({
shortCode: req.params.shortCode,
fingerprint: fingerprint,
timestamp: new Date(),
});
// Redirect to App Store/Google Play
res.redirect(302, getAppStoreUrl(req));
});
2. Collect fingerprint at install time:
// Mobile SDK (collect on first app open)
const fingerprint = {
ip: await getPublicIP(), // From server
userAgent: DeviceInfo.getUserAgent(),
screen: {
width: Dimensions.get('window').width * PixelRatio.get(),
height: Dimensions.get('window').height * PixelRatio.get(),
},
language: DeviceInfo.getDeviceLocale(),
timezone: RNLocalize.getTimeZone(),
carrier: await DeviceInfo.getCarrier(),
os: {
name: Platform.OS,
version: DeviceInfo.getSystemVersion(),
},
device: {
model: DeviceInfo.getModel(),
brand: DeviceInfo.getBrand(),
},
};
// Send to server for matching
const response = await fetch('https://yourapi.com/attribution/match', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fingerprint }),
});
const attribution = await response.json();
3. Match fingerprints:
// Server-side matching algorithm
async function matchFingerprint(installFingerprint) {
// Find recent clicks (within attribution window)
const recentClicks = await db.clicks.find({
timestamp: {
$gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days
},
});
// Score each click based on similarity
const scores = recentClicks.map(click => ({
click,
score: calculateSimilarity(click.fingerprint, installFingerprint),
}));
// Sort by score (highest first)
scores.sort((a, b) => b.score - a.score);
// Return best match if score > threshold
const bestMatch = scores[0];
if (bestMatch && bestMatch.score > 0.7) { // 70% threshold
return {
matched: true,
attribution: bestMatch.click,
confidence: bestMatch.score,
};
}
return { matched: false };
}
function calculateSimilarity(fp1, fp2) {
let score = 0;
let maxScore = 0;
// IP address (weight: 30%)
maxScore += 30;
if (fp1.ip === fp2.ip) {
score += 30;
} else if (isSameSubnet(fp1.ip, fp2.ip)) {
score += 15; // Half credit for same subnet
}
// Screen resolution (weight: 20%)
maxScore += 20;
if (fp1.screen?.width === fp2.screen?.width &&
fp1.screen?.height === fp2.screen?.height) {
score += 20;
}
// Language (weight: 10%)
maxScore += 10;
if (fp1.language === fp2.language) {
score += 10;
}
// Timezone (weight: 10%)
maxScore += 10;
if (fp1.timezone === fp2.timezone) {
score += 10;
}
// OS version (weight: 10%)
maxScore += 10;
if (fp1.os?.version === fp2.os?.version) {
score += 10;
}
// Device model (weight: 20%)
maxScore += 20;
if (fp1.device?.model === fp2.device?.model) {
score += 20;
}
return score / maxScore; // Normalized 0-1
}
Time to build: 2-4 weeks
Challenges:
- IP address handling (proxies, VPNs, carrier NAT)
- False positive prevention
- Attribution window management
- Scale (matching millions of clicks/installs)
Recommendation: Use LinkForty instead (saves 2-4 weeks, battle-tested)
Accuracy Expectations
Fingerprinting Accuracy by Scenario
| Scenario | Accuracy | Explanation |
|---|---|---|
| Same WiFi, <1 hour | 90-95% | Ideal conditions |
| Same cellular, <24h | 80-85% | Cellular IPs more unique |
| Different network | 60-70% | IP changed, other factors match |
| Shared WiFi (office) | 50-60% | Multiple devices, same IP |
| Long time (3-7 days) | 65-75% | IP may have changed |
| VPN user | 40-50% | VPN changes IP, other factors still match |
LinkForty achieves: 70-80% average accuracy (industry standard)
Comparing to IDFA
| Method | Accuracy | Coverage | Privacy | Permission Required |
|---|---|---|---|---|
| IDFA (pre-iOS 14.5) | 95-100% | 100% | ❌ Low | ❌ No |
| IDFA (post-iOS 14.5) | 95-100% | ~30% | ❌ Low | ✅ Yes (ATT) |
| Fingerprinting | 70-80% | 100% | ✅ High | ❌ No |
| SKAdNetwork | 100%* | 100% | ✅ High | ❌ No |
*SKAdNetwork is 100% accurate but anonymized (no user-level data)
Best Practices for Post-ATT Attribution
1. Don't Show ATT Prompt (Unless You Have Good Reason)
Bad:
// App launch
ATTrackingManager.requestTrackingAuthorization { status in
// 70% of users will decline
}
Why bad:
- 70% of users decline anyway
- Negative first impression
- Reduces app quality perception
- Alternatives work without permission
Good:
// Don't show ATT prompt at all
// Use fingerprinting instead (70-80% accuracy for everyone)
Exception: Only show ATT if you have a compelling reason:
- Personalized ads (explain the benefit)
- Cross-app experiences
- Genuine value to user
2. Use Fingerprinting as Primary Method
Strategy:
Primary: Fingerprinting (works for 100% of users, 70-80% accuracy)
Fallback: IDFA (if user grants permission - bonus data for ~30%)
Implementation:
// Check for fingerprint attribution first
const attribution = await LinkForty.getAttributionData();
if (attribution.matched) {
// Got attribution via fingerprinting
trackAttributedInstall(attribution);
}
// Optionally check IDFA (if you have a reason to ask)
// Most apps should skip this
3. Shorten Attribution Windows
Old approach (IDFA era):
- 30-90 day attribution windows common
- IDFA matching was 100% accurate even after weeks
New approach (fingerprinting):
- 7-day attribution window recommended
- Accuracy decreases over time (IP changes, settings changes)
Optimal windows:
- 7 days: Standard (recommended)
- 24-48 hours: High-intent campaigns (flash sales)
- 14 days: Consideration products (max)
4. Track Campaign Performance, Not Individual Users
Old mindset (IDFA):
- Track every user's journey
- Detailed user-level attribution
- Cross-app tracking
New mindset (privacy-first):
- Track campaign performance (aggregate)
- Understand which channels drive installs
- Respect user privacy
Example:
// Good: Campaign-level insights
Dashboard shows:
- Instagram campaign: 1,000 installs, $5 CPI
- Facebook campaign: 500 installs, $8 CPI
→ Optimize budget allocation
// Don't need: Individual user tracking
// "User ABC123 clicked ad at 2pm, installed at 3pm, made purchase at 5pm"
// This is unnecessary and privacy-invasive
5. Combine with SKAdNetwork for Paid Ads
For paid advertising (Facebook, Google, TikTok):
Use BOTH:
1. SKAdNetwork (for ad platform attribution)
2. Fingerprinting (for your own analytics)
Why both:
- SKAdNetwork gives ad platforms attribution data (required)
- Fingerprinting gives you real-time, detailed analytics
- Together: Complete picture
Privacy Compliance
GDPR Compliance
Is fingerprinting GDPR-compliant?
Yes, when implemented correctly:
Why it's compliant:
- Not personal data: Device characteristics ≠ personally identifiable information
- Legitimate interest: Attribution is necessary business function
- Temporary: Data used only during attribution window (7 days)
- No cross-app tracking: Only tracks within your app's attribution
Legal basis: Legitimate interest (Article 6(1)(f) GDPR)
User Rights Under GDPR
You must support:
- Right to access: User can request attribution data
- Right to deletion: User can delete attribution records
- Right to object: User can opt-out of attribution
Implementation:
// Opt-out of attribution
await LinkForty.optOut();
// Delete attribution data
await LinkForty.deleteUserData();
Privacy Policy Requirements
Your privacy policy should disclose:
## Mobile Attribution
We use device fingerprinting to attribute app installs to marketing campaigns.
**Data collected:**
- IP address (anonymized after 7 days)
- Device characteristics (screen size, language, timezone)
- Click timestamp
**Purpose:** Understanding which marketing campaigns drive app installs
**Retention:** 7 days (deleted after attribution window)
**Legal basis:** Legitimate interest (GDPR Article 6(1)(f))
**Your rights:** You can request deletion of attribution data at any time
Cost Comparison
IDFA-Based Attribution Platforms (Pre-iOS 14.5)
AppsFlyer, Adjust, Branch:
- Pricing: $500-$5,000+/month
- Relied heavily on IDFA
- Post-ATT: Had to pivot to fingerprinting anyway
- Result: Same attribution method, but expensive
Modern Fingerprinting Platforms
LinkForty:
- Pricing: Free (self-hosted) or $29/month (cloud)
- Built for post-ATT era from day one
- Privacy-first by design
- 70-80% accuracy (industry standard)
ROI:
Enterprise MMP: $2,000/month = $24,000/year
LinkForty Cloud: $29/month = $348/year
Savings: $23,652/year for same attribution accuracy
Summary
iOS 14.5 changed mobile attribution forever. IDFA-based tracking went from 100% coverage to ~30% overnight. The industry adapted with fingerprint attribution as the new standard.
Key Takeaways:
- ✅ Fingerprinting is the new standard (70-80% accuracy, no permission needed)
- ✅ Don't ask for ATT permission unless you have a compelling reason
- ✅ Focus on campaign performance, not individual user tracking
- ✅ Use LinkForty for privacy-first, cost-effective attribution
- ✅ Combine with SKAdNetwork for paid ad attribution
The future is privacy-first attribution.
Get Started with LinkForty
Cloud (5-minute setup):
- Sign up at linkforty.com
- Create your first link
- Integrate SDK
- Get 70-80% attribution without asking for permission
Self-Hosted (Free):
npm install @linkforty/core- Deploy to your infrastructure
- Full data ownership
Next Steps
- What is Mobile Attribution? - Attribution fundamentals
- Deferred Deep Linking - How fingerprinting works
- Privacy-First Attribution - GDPR compliance guide
- Attribution Models - Comparison of methods
Questions?