Skip to main content

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:

  1. User clicks ad → IDFA recorded (8C9B7F7E...)
  2. User installs app → IDFA read (8C9B7F7E...)
  3. 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

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

ScenarioAccuracyExplanation
Same WiFi, <1 hour90-95%Ideal conditions
Same cellular, <24h80-85%Cellular IPs more unique
Different network60-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 user40-50%VPN changes IP, other factors still match

LinkForty achieves: 70-80% average accuracy (industry standard)


Comparing to IDFA

MethodAccuracyCoveragePrivacyPermission Required
IDFA (pre-iOS 14.5)95-100%100%❌ Low❌ No
IDFA (post-iOS 14.5)95-100%~30%❌ Low✅ Yes (ATT)
Fingerprinting70-80%100%✅ High❌ No
SKAdNetwork100%*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:

  1. Not personal data: Device characteristics ≠ personally identifiable information
  2. Legitimate interest: Attribution is necessary business function
  3. Temporary: Data used only during attribution window (7 days)
  4. 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:

  1. Right to access: User can request attribution data
  2. Right to deletion: User can delete attribution records
  3. 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):

  1. Sign up at linkforty.com
  2. Create your first link
  3. Integrate SDK
  4. Get 70-80% attribution without asking for permission

Self-Hosted (Free):

  1. npm install @linkforty/core
  2. Deploy to your infrastructure
  3. Full data ownership

Get Started →

View SDK Integration Guide →


Next Steps


Questions?