Skip to main content

Migrating from Firebase Dynamic Links to LinkForty

Firebase Dynamic Links (FDL) was deprecated by Google on August 25, 2025 and will shut down completely on August 25, 2025. If you're using Firebase Dynamic Links, you need to migrate to an alternative solution.

This guide provides a complete migration path from Firebase Dynamic Links to LinkForty.


Google announced the deprecation of Firebase Dynamic Links citing:

  1. Low adoption compared to other Firebase products
  2. High maintenance cost for Google
  3. Platform-native alternatives (Universal Links, App Links) now mature
  4. Shift in strategy - Google recommending platform-native solutions

Official announcement: Firebase Blog (August 2025)


DateEvent
August 25, 2025Deprecation announced, new link creation disabled
August 25, 2025Existing links continue to work (redirect only)
August 25, 2025Full shutdown - all links stop working

⚠️ CRITICAL: If you don't migrate by August 25, 2025, all your Firebase Dynamic Links will stop working (404 errors).


Feature Comparison

FeatureFirebase Dynamic LinksLinkForty
PricingFreeFree (self-hosted) or $29/month (cloud)
Status❌ Deprecated (shutting down)✅ Active, growing
Short Links✅ Yes (page.link domain)✅ Yes (custom domain)
Deferred Deep Linking✅ Yes✅ Yes (70-80% accuracy)
AnalyticsBasic✅ Comprehensive (click tracking, attribution, conversions)
Custom Domains❌ No (only page.link)✅ Yes (bring your own domain)
Universal/App Links✅ Auto-generated✅ Auto-generated
Open Source❌ No✅ Yes (MIT license)
Self-Hosted Option❌ No✅ Yes
Data Ownership❌ Google owns data✅ You own data (self-hosted)
UTM Tracking✅ Yes✅ Yes
QR Codes❌ No✅ Yes
Webhooks❌ No✅ Yes
API Access✅ Limited✅ Full REST API
Team Collaboration❌ No✅ Yes (cloud)

What You Gain with LinkForty

✅ More Features:

  • Custom domains (vs Firebase's page.link only)
  • QR code generation
  • Webhooks for real-time events
  • Team collaboration (cloud)
  • Advanced analytics (conversion tracking, campaign ROI)

✅ Data Ownership:

  • Self-hosted option (full control)
  • No vendor lock-in (open source)
  • Export data anytime

✅ Privacy-First:

  • GDPR compliant by design
  • No third-party data sharing
  • Transparent attribution

✅ Cost-Effective:

  • Free forever (self-hosted)
  • $29/month (managed cloud) - predictable pricing
  • No usage-based pricing surprises

What You Lose (and How LinkForty Handles It)

❌ Firebase Console Integration

  • Firebase DL was integrated into Firebase Console
  • LinkForty solution: Dedicated dashboard with better UX

❌ Automatic Google Analytics Integration

  • Firebase DL sent events to Google Analytics automatically
  • LinkForty solution: Webhooks to send events to any analytics platform (including GA)

page.link Domain

  • Firebase provided yourapp.page.link domain
  • LinkForty solution: Use your own domain (better branding) like go.yourapp.com

❌ Firebase Auth Integration

  • Firebase DL integrated with Firebase Auth for attribution
  • LinkForty solution: Works with any auth system via API

Migration Guide: Step-by-Step

Phase 1: Setup LinkForty (15 minutes)

Option A: LinkForty Cloud (Fastest)

1. Create account:

Visit: https://linkforty.com
Sign up (no credit card required)
Create organization

2. Configure app details:

Settings → App Configuration

iOS:
- Team ID: [Your Apple Team ID]
- Bundle ID: com.yourapp
- App Store URL: https://apps.apple.com/app/yourapp/id123456

Android:
- Package Name: com.yourapp
- SHA-256 Fingerprint: [Your keystore fingerprint]
- Google Play URL: https://play.google.com/store/apps/details?id=com.yourapp

3. Set up custom domain (optional but recommended):

Settings → Custom Domains
Add domain: go.yourapp.com
Configure DNS: CNAME go.yourapp.com → your-org.linkforty.app

4. Get API key:

Settings → API Keys
Create API Key → Copy and save securely

Option B: Self-Hosted (Free)

1. Install LinkForty Core:

npm install @linkforty/core

2. Set up infrastructure:

# PostgreSQL (required)
docker run -d \
--name linkforty-postgres \
-e POSTGRES_PASSWORD=yourpassword \
-e POSTGRES_DB=linkforty \
-p 5432:5432 \
postgres:15

# Redis (optional, improves performance)
docker run -d \
--name linkforty-redis \
-p 6379:6379 \
redis:7

3. Configure environment:

# .env
DATABASE_URL=postgresql://postgres:yourpassword@localhost:5432/linkforty
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-super-secret-key-change-this
FRONTEND_URL=https://yourapp.com

4. Run migrations:

cd node_modules/@linkforty/core
npm run migrate

5. Start server:

npm start

Full Self-Hosting Guide →


https://yourapp.page.link/abc123

Parameters:
- link: https://yourapp.com/products/123
- apn: com.yourapp (Android package)
- ibi: com.yourapp (iOS bundle ID)
- isi: 123456 (App Store ID)
- utm_source: instagram
- utm_campaign: summer-sale

LinkForty Equivalent:

Create link template first:

Dashboard → Settings → Link Templates → Create Template

Name: Default Template
iOS URL: https://apps.apple.com/app/yourapp/id123456
Android URL: https://play.google.com/store/apps/details?id=com.yourapp
Attribution Window: 7 days

Create link:

Dashboard → Links → Create Link

Template: Default Template
Original URL: https://yourapp.com/products/123
Title: Product 123 Campaign
UTM Source: instagram
UTM Campaign: summer-sale

Generated link: https://go.yourapp.com/abc123

Or use API:

curl -X POST https://api.linkforty.com/api/links \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "template_abc123",
"originalUrl": "https://yourapp.com/products/123",
"title": "Product 123 Campaign",
"utmParams": {
"source": "instagram",
"campaign": "summer-sale"
}
}'

Response:

{
"id": "link_xyz789",
"shortCode": "abc123",
"shortUrl": "https://go.yourapp.com/abc123",
"originalUrl": "https://yourapp.com/products/123"
}

Phase 3: Migrate SDK (30-60 minutes)

iOS Migration

Remove Firebase Dynamic Links SDK:

Before (Firebase):

// Podfile
pod 'Firebase/DynamicLinks'
// AppDelegate.swift
import Firebase

func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
return true
}

// Handle Firebase Dynamic Link
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) {
handleDynamicLink(dynamicLink)
return true
}
return false
}

func handleDynamicLink(_ dynamicLink: DynamicLink) {
guard let url = dynamicLink.url else { return }
// Route to content
navigateToContent(url: url)
}

After (LinkForty):

# Install LinkForty SDK
# Coming soon: pod 'LinkFortySDK'

# For now, use REST API approach (see below)

REST API approach (works now):

import Foundation

class LinkFortySDK {
static let shared = LinkFortySDK()
private let apiKey = "your-api-key"
private let baseUrl = "https://api.linkforty.com"

func checkAttribution() async -> AttributionData? {
// Collect device fingerprint
let fingerprint = collectFingerprint()

// Send to LinkForty
var request = URLRequest(url: URL(string: "\(baseUrl)/api/attribution/match")!)
request.httpMethod = "POST"
request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")

let body = [
"fingerprint": fingerprint
]
request.httpBody = try? JSONEncoder().encode(body)

guard let (data, _) = try? await URLSession.shared.data(for: request),
let response = try? JSONDecoder().decode(AttributionResponse.self, from: data),
response.matched else {
return nil
}

return response.attribution
}

private func collectFingerprint() -> [String: Any] {
let screen = UIScreen.main
return [
"screen_width": Int(screen.bounds.width * screen.scale),
"screen_height": Int(screen.bounds.height * screen.scale),
"language": Locale.current.languageCode ?? "en",
"timezone": TimeZone.current.identifier,
"os_version": UIDevice.current.systemVersion,
"device_model": UIDevice.current.model
]
}
}

struct AttributionResponse: Codable {
let matched: Bool
let attribution: AttributionData?
}

struct AttributionData: Codable {
let destination: String
let utmSource: String?
let utmCampaign: String?
let customParams: [String: String]?
}

Use in AppDelegate:

func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Remove: FirebaseApp.configure()

// Check for deferred deep link attribution
Task {
if let attribution = await LinkFortySDK.shared.checkAttribution() {
handleAttribution(attribution)
}
}

return true
}

func handleAttribution(_ attribution: AttributionData) {
// Route to destination
let path = attribution.destination
navigateToContent(path: path)

// Track campaign
if let campaign = attribution.utmCampaign {
analytics.track("attributed_install", properties: ["campaign": campaign])
}
}

Handle Universal Links (same as before):

func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}

// LinkForty handles Universal Links automatically
// Just route to content
navigateToContent(url: url)
return true
}

Android Migration

Remove Firebase Dynamic Links SDK:

Before (Firebase):

// build.gradle
dependencies {
implementation 'com.google.firebase:firebase-dynamic-links:21.1.0'
}
// MainActivity.kt
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Handle Firebase Dynamic Link
FirebaseDynamicLinks.getInstance()
.getDynamicLink(intent)
.addOnSuccessListener { pendingDynamicLinkData ->
val deepLink = pendingDynamicLinkData?.link
deepLink?.let { handleDeepLink(it) }
}
}

fun handleDeepLink(uri: Uri) {
val path = uri.path
// Route to content
navigateToContent(path)
}

After (LinkForty):

# Install LinkForty SDK
# Coming soon: implementation 'com.linkforty:sdk:1.0.0'

# For now, use REST API approach (see below)

REST API approach (works now):

import kotlinx.coroutines.*
import okhttp3.*
import org.json.JSONObject

class LinkFortySDK(private val apiKey: String, private val baseUrl: String = "https://api.linkforty.com") {
private val client = OkHttpClient()

suspend fun checkAttribution(): AttributionData? = withContext(Dispatchers.IO) {
try {
val fingerprint = collectFingerprint()

val json = JSONObject().apply {
put("fingerprint", fingerprint)
}

val request = Request.Builder()
.url("$baseUrl/api/attribution/match")
.addHeader("Authorization", "Bearer $apiKey")
.addHeader("Content-Type", "application/json")
.post(RequestBody.create(
MediaType.parse("application/json"),
json.toString()
))
.build()

val response = client.newCall(request).execute()
val responseBody = response.body()?.string() ?: return@withContext null

val jsonResponse = JSONObject(responseBody)
if (jsonResponse.getBoolean("matched")) {
val attribution = jsonResponse.getJSONObject("attribution")
AttributionData(
destination = attribution.getString("destination"),
utmSource = attribution.optString("utmSource"),
utmCampaign = attribution.optString("utmCampaign")
)
} else {
null
}
} catch (e: Exception) {
null
}
}

private fun collectFingerprint(): JSONObject {
val displayMetrics = Resources.getSystem().displayMetrics
return JSONObject().apply {
put("screen_width", displayMetrics.widthPixels)
put("screen_height", displayMetrics.heightPixels)
put("language", Locale.getDefault().language)
put("timezone", TimeZone.getDefault().id)
put("os_version", Build.VERSION.RELEASE)
put("device_model", Build.MODEL)
}
}
}

data class AttributionData(
val destination: String,
val utmSource: String?,
val utmCampaign: String?
)

Use in MainActivity:

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Check for deferred deep link attribution
lifecycleScope.launch {
val sdk = LinkFortySDK(apiKey = "your-api-key")
val attribution = sdk.checkAttribution()

attribution?.let {
handleAttribution(it)
}
}
}

fun handleAttribution(attribution: AttributionData) {
// Route to destination
navigateToContent(attribution.destination)

// Track campaign
attribution.utmCampaign?.let { campaign ->
analytics.track("attributed_install", mapOf("campaign" to campaign))
}
}

Handle App Links (same as before):

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.data?.let { uri ->
// LinkForty handles App Links automatically
handleDeepLink(uri)
}
}

React Native Migration

Before (Firebase):

import dynamicLinks from '@react-native-firebase/dynamic-links';

useEffect(() => {
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
return () => unsubscribe();
}, []);

const handleDynamicLink = (link) => {
const url = link.url;
// Route to content
navigateToContent(url);
};

After (LinkForty):

# Install LinkForty SDK
npm install @linkforty/react-native-sdk
import LinkForty from '@linkforty/react-native-sdk';
import { Linking } from 'react-native';

useEffect(() => {
// Initialize LinkForty
LinkForty.init({
baseUrl: 'https://api.linkforty.com',
apiKey: 'your-api-key',
debug: __DEV__,
});

// Check for deferred deep link attribution
checkAttribution();

// Handle regular deep links
const subscription = Linking.addEventListener('url', ({ url }) => {
handleDeepLink(url);
});

return () => subscription.remove();
}, []);

const checkAttribution = async () => {
const attribution = await LinkForty.getAttributionData();

if (attribution && attribution.matched) {
// User came from a link, route to destination
console.log('Attribution:', attribution);
navigateToContent(attribution.destination);

// Track attributed install
analytics.track('attributed_install', {
campaign: attribution.utmCampaign,
source: attribution.utmSource,
});
}
};

const handleDeepLink = (url: string) => {
const path = url.replace('https://go.yourapp.com', '');
navigateToContent(path);
};

Before:

// Firebase Dynamic Links REST API
const response = await fetch('https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=API_KEY', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
dynamicLinkInfo: {
domainUriPrefix: 'https://yourapp.page.link',
link: 'https://yourapp.com/products/123',
androidInfo: {
androidPackageName: 'com.yourapp',
},
iosInfo: {
iosBundleId: 'com.yourapp',
iosAppStoreId: '123456',
},
socialMetaTagInfo: {
socialTitle: 'Product Title',
socialDescription: 'Product description',
},
},
}),
});

const { shortLink } = await response.json();
// shortLink: https://yourapp.page.link/abc123

LinkForty API:

After:

const response = await fetch('https://api.linkforty.com/api/links', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: 'template_default', // Pre-configured with iOS/Android URLs
originalUrl: 'https://yourapp.com/products/123',
title: 'Product 123',
description: 'Product description',
utmParams: {
source: 'instagram',
campaign: 'summer-sale',
},
}),
});

const { shortUrl } = await response.json();
// shortUrl: https://go.yourapp.com/abc123

Phase 5: Migrate Analytics

Firebase Analytics Events:

Firebase Dynamic Links sent events to Firebase Analytics automatically:

  • dynamic_link_first_open - First app open from dynamic link
  • dynamic_link_app_open - Subsequent opens from dynamic link

LinkForty Webhooks:

LinkForty provides webhooks for real-time events:

1. Configure webhook:

Dashboard → Settings → Webhooks → Create Webhook

URL: https://yourapp.com/webhooks/linkforty
Events:
- link.clicked
- app.installed
- deeplink.matched

2. Receive events:

// Express.js webhook endpoint
app.post('/webhooks/linkforty', (req, res) => {
const { event, data } = req.body;

switch (event) {
case 'link.clicked':
// User clicked link
analytics.track('Link Clicked', {
linkId: data.linkId,
campaign: data.utmCampaign,
});
break;

case 'app.installed':
// User installed app (deferred deep link matched)
analytics.track('App Installed', {
linkId: data.linkId,
campaign: data.utmCampaign,
source: data.utmSource,
});

// Send to Google Analytics if needed
sendToGoogleAnalytics({
event: 'dynamic_link_first_open',
campaign: data.utmCampaign,
});
break;

case 'deeplink.matched':
// Deferred deep link successfully matched
analytics.track('Deeplink Matched', {
destination: data.destination,
});
break;
}

res.sendStatus(200);
});

Phase 6: Testing

1. Test link creation:

Create test link in LinkForty dashboard
Verify short URL works: https://go.yourapp.com/test123

2. Test with app installed:

1. Have app installed
2. Click test link
3. Verify app opens (not browser)
4. Verify routes to correct content

3. Test deferred deep linking (app NOT installed):

1. Uninstall app
2. Click test link
3. Redirected to App Store/Google Play ✓
4. Install app
5. Open app
6. Verify routes to correct content ✓
7. Verify attribution tracked in dashboard ✓

4. Test analytics:

1. Create test link with UTM parameters
2. Click link, install app
3. Check LinkForty dashboard → Analytics
4. Verify click and install tracked ✓
5. Check webhook endpoint
6. Verify events received ✓

Phase 7: Go Live

1. Update production links:

Replace all yourapp.page.link URLs with go.yourapp.com URLs
Update:
- Marketing campaigns
- Email templates
- Social media bios
- Print materials
- QR codes

2. Set up redirects (if needed):

If you have many existing Firebase Dynamic Links in the wild:

Option A: Let them expire (Firebase shuts down Aug 25, 2025)
Option B: Set up redirect service to catch old links and redirect to new LinkForty links

3. Monitor:

Dashboard → Analytics
- Click rates
- Install attribution
- Conversion rates
- Campaign performance

Migration Checklist

  • Phase 1: Set up LinkForty (Cloud or Self-Hosted)
  • Phase 2: Create link templates and test links
  • Phase 3: Migrate mobile SDKs (iOS, Android, React Native)
  • Phase 4: Update link generation (replace Firebase API calls)
  • Phase 5: Set up analytics webhooks
  • Phase 6: Test thoroughly (installed app + new installs)
  • Phase 7: Update production links and go live

Common Migration Issues

Cause: Verification files not set up correctly

Solution:

  1. Check LinkForty generated verification files:
    • https://go.yourapp.com/.well-known/apple-app-site-association
    • https://go.yourapp.com/.well-known/assetlinks.json
  2. Verify accessible (200 OK, no redirects)
  3. Uninstall and reinstall app (iOS caches AASA file)

Issue 2: "Attribution accuracy lower than expected"

Cause: Fingerprinting vs Firebase's device ID approach

Solution:

  • Fingerprinting achieves 70-80% accuracy (industry standard for privacy-friendly attribution)
  • Firebase used device IDs (higher accuracy but requires user permission post-iOS 14.5)
  • To improve: Shorten attribution window, use Universal/App Links

Issue 3: "Missing Firebase Console integration"

Cause: LinkForty is standalone (not part of Firebase)

Solution:

  • Use LinkForty dashboard for link management
  • Use webhooks to send events to Firebase Analytics (if needed)

Cost Comparison

  • Pricing: Free
  • Status: Shutting down (no longer an option)

LinkForty:

  • Self-Hosted: Free forever (MIT license)
  • Cloud: $29/month (unlimited links, unlimited clicks)

ROI: $29/month to replace a shutdown service with MORE features is excellent value.


Support & Help

Need migration help?


Frequently Asked Questions

August 25, 2025 - All Firebase Dynamic Links will stop working (404 errors).

Yes, existing links will work until August 25, 2025. However, you cannot create new Firebase Dynamic Links after August 25, 2025.

No. You need to:

  1. Create equivalent links in LinkForty
  2. Update your marketing materials with new LinkForty URLs
  3. (Optional) Set up redirect service for old Firebase URLs if needed

How long does migration take?

  • Simple apps: 1-2 days (SDK update + testing)
  • Complex apps: 1 week (SDK update + link migration + analytics setup + testing)

Firebase was free but is shutting down. LinkForty offers:

  • Free (self-hosted) - same cost as Firebase, more control
  • $29/month (cloud) - managed service with better features than Firebase ever had

Can I use LinkForty with Firebase Authentication?

Yes! LinkForty works with any authentication system. Use webhooks to send attribution data to your Firebase project.

Does LinkForty support custom domains like Firebase?

Better! Firebase only allowed yourapp.page.link. LinkForty lets you use any domain you own (e.g., go.yourapp.com, link.yourapp.com).


Summary

Firebase Dynamic Links is shutting down on August 25, 2025. You must migrate before this date or all your links will stop working.

LinkForty provides:

  • ✅ All Firebase Dynamic Links features (and more)
  • ✅ Simple migration path (1-2 days for most apps)
  • ✅ Better analytics and webhooks
  • ✅ Custom domains
  • ✅ Open source & self-hostable
  • ✅ Privacy-first approach
  • ✅ Free (self-hosted) or affordable ($29/month cloud)

Ready to migrate?

Get Started with LinkForty →

View Complete Documentation →

Contact for Migration Help →


Questions?