The referral attribution challenge
Referral programs drive user growth, but attribution breaks when referral links are tapped before the app is installed. Here's the problem:
What breaks without deferred deep links:
- 1.User A refers User B via a referral link:
https://myapp.com/ref/alice123 - 2.User B doesn't have the app installed. They tap the link, land on a web page.
- 3.They're prompted to install. They go to App Store. Referrer context is lost.
- 4.App launches. Your SDK doesn't know who referred User B. Attribution is broken.
- 5.User A never gets credit for the referral. No reward. Referral loop collapses.
✕Without deferred deep links
- •Referrer ID lost when user taps link before install
- •No way to track who invited new user
- •Rewards can't be distributed correctly
- •Referral program incentive broken
With deferred deep links
- ✓Referrer ID preserved across app install
- ✓First install attributed to correct referrer
- ✓Rewards automatically credited to referrer
- ✓Viral growth loop stays intact
How deferred deep links enable attribution
Step 1: Generate referral link
When User A wants to refer friends, you generate a Redirectly deep link with their referrer ID: https://myapp.redirectly.app/ref?referrer_id=alice123
Step 2: User shares link
User A shares the link via SMS, email, or social. User B receives it on mobile and taps it.
Step 3: Redirectly captures referrer ID
If User B has the app: deep link opens directly, referrer ID is passed to your app immediately.
If User B doesn't have the app: Redirectly captures the full URL (including referrer_id) and stores it.
Step 4: App store redirect
User B is sent to App Store. Referrer ID is preserved in Redirectly's servers—not lost.
Step 5: SDK queries deferred link on first launch
When your app launches for the first time on User B's device, the Redirectly SDK asks: "What deep link was intended for this device?" Redirectly responds with the referral link including referrer_id.
Step 6: Credit referrer and award points
Your app receives the referrer_id, posts it to your backend: "User B was referred by alice123." Your backend credits User A with the referral reward instantly.
Implementation with Redirectly
Backend: Generate referral links
When a user requests their referral link, create a Redirectly deep link with their ID:
# Backend code (Node.js)
app.get('/api/get-referral-link', (req, res) => {
const userId = req.user.id;
const referralLink = (
'https://myapp.redirectly.app/ref'
+ '?referrer_id=' + userId
+ '&campaign=user_referral'
);
// Store the referral link in DB for tracking
saveReferralLink(userId, referralLink);
res.json({ referral_link: referralLink });
});Mobile: Initialize and listen
Setup Redirectly and handle referral deep links on first launch:
import 'package:redirectly/redirectly.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Redirectly.initialize(
apiKey: 'your_api_key_here',
);
// Get deferred link (called on first launch)
final deepLink = await Redirectly.getInitialLink();
if (deepLink != null && deepLink.path == 'ref') {
final referrerId = deepLink.queryParameters['referrer_id'];
print('Referred by: $referrerId');
// Send to backend to credit referrer
creditReferral(referrerId);
}
runApp(const MyApp());
}Backend: Credit the referrer
When a new user is attributed to a referrer, award points or credits:
app.post('/api/credit-referral', async (req, res) => {
const { referrer_id } = req.body;
// Check if referrer exists
const referrer = await User.findById(referrer_id);
if (!referrer) {
return res.status(400).json({ error: 'Invalid referrer' });
}
// Award points to referrer
referrer.referral_points += 50; // or 5, depending on your rewards
referrer.total_referrals += 1;
await referrer.save();
// Log the referral for analytics
await ReferralEvent.create({
referrer_id,
referred_user_id: req.user.id,
timestamp: new Date(),
reward_amount: 50,
});
res.json({ success: true, points_awarded: 50 });
});Complete referral program example
// Flutter app - Handle referral on first launch
import 'package:redirectly/redirectly.dart';
import 'package:http/http.dart' as http;
class ReferralManager {
static Future<void> handleReferralOnFirstLaunch() async {
// Get the deferred deep link
final deepLink = await Redirectly.getInitialLink();
if (deepLink != null && deepLink.path == 'ref') {
final referrerId = deepLink.queryParameters['referrer_id'];
if (referrerId != null && referrerId.isNotEmpty) {
await _creditReferrer(referrerId);
}
}
}
static Future<void> _creditReferrer(String referrerId) async {
try {
final response = await http.post(
Uri.parse('https://api.example.com/api/credit-referral'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${authToken}',
},
body: json.encode({
'referrer_id': referrerId,
}),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
final pointsAwarded = data['points_awarded'];
print('Referrer credited with $pointsAwarded points');
// Show celebration UI to new user
showReferralBonus(referrerId, pointsAwarded);
}
} catch (e) {
print('Error crediting referrer: $e');
}
}
}
// User profile - Show referral link
class ReferralLinkWidget extends StatefulWidget {
final User user;
const ReferralLinkWidget({required this.user});
@override
State<ReferralLinkWidget> createState() => _ReferralLinkWidgetState();
}
class _ReferralLinkWidgetState extends State<ReferralLinkWidget> {
String? referralLink;
bool isLoading = true;
@override
void initState() {
super.initState();
_fetchReferralLink();
}
Future<void> _fetchReferralLink() async {
try {
final response = await http.get(
Uri.parse('https://api.example.com/api/get-referral-link'),
headers: {
'Authorization': 'Bearer ${authToken}',
},
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
setState(() {
referralLink = data['referral_link'];
isLoading = false;
});
}
} catch (e) {
setState(() => isLoading = false);
}
}
Future<void> _copyToClipboard() async {
if (referralLink != null) {
await Clipboard.setData(ClipboardData(text: referralLink!));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Referral link copied!')),
);
}
}
Future<void> _shareLink() async {
if (referralLink != null) {
await Share.share(
'Join me on the app! $referralLink',
subject: 'Join my community',
);
}
}
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Your Referral Link',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
if (isLoading)
const CircularProgressIndicator()
else if (referralLink != null) ...[
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: SelectableText(
referralLink!,
style: const TextStyle(fontSize: 12),
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _copyToClipboard,
icon: const Icon(Icons.copy),
label: const Text('Copy'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: _shareLink,
icon: const Icon(Icons.share),
label: const Text('Share'),
),
),
],
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
const Icon(Icons.info, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
'Earn 50 points for each friend who joins!',
style: TextStyle(fontSize: 12, color: Colors.blue[900]),
),
),
],
),
),
] else
const Text('Failed to load referral link'),
],
),
),
);
}
}This example includes:
- Deferred link detection on first launch
- API call to backend to credit the referrer
- Referral link generator in user profile
- Share and copy buttons for easy distribution
Next steps
Learn more about referral programs and attribution:
Frequently asked questions
What if the referrer ID is invalid?
On your backend, validate the referrer ID against your users table before awarding points. If invalid, simply don't credit anything. The new user still gets onboarded normally.
Can I track multiple referrals per user?
Yes. Each time a deferred link is detected with a referrer_id, you can log a new ReferralEvent. Track all historical referrals in your database.
What rewards should I offer?
Common approaches: 50-500 points per referral, premium features unlocked, or cash/credit rewards. The amount depends on your unit economics and user value.
How do I prevent referral fraud?
Validate that the new user is not already in your system. Check their IP/device ID for obvious duplicates. Consider requiring email verification before awarding points.
Do I need to handle this differently for Android vs iOS?
No. The Redirectly SDK handles the platform differences. Your code is the same for both.