Click injection is a mobile ad fraud technique specific to Android. A malicious app on the user's device listens for app installs via a broadcast receiver, then fires a fake ad click just before the app opens for the first time. The attribution system sees the click and credits the install to the fraudulent ad network, which collects the CPI payout for an install it had nothing to do with.
This guide explains how click injection works, how to detect it, and how to prevent it. For attribution fraud broadly, see attribution fraud: detection and prevention guide. For mobile attribution, see mobile attribution: a developer's guide.
How Click Injection Works
The Android Broadcast
On Android, the system sends a com.android.vending.INSTALL_REFERRER broadcast when an app is installed from the Google Play Store. Before Google restricted this broadcast, any app could listen for it:
1. User sees a legitimate ad and decides to install the app
2. User downloads from Google Play Store
3. Android broadcasts INSTALL_REFERRER
4. Malicious app on the device intercepts the broadcast
5. Malicious app fires a fake ad click with matching device ID
6. App opens for the first time
7. Attribution provider sees the fake click (most recent) and attributes the install to it
8. Fraudulent network gets paid for the install
The key exploit is the timing: the malicious app knows an install is happening (from the broadcast) and fires the click before the app opens. Since attribution systems use last-click, the fake click wins.
Why It Only Affects Android
iOS does not broadcast install events to other apps. Each iOS app runs in its own sandbox and cannot observe other apps being installed. Click injection is an Android-specific problem.
Detection Signals
Click-to-Install Time (CTIT)
The most reliable detection signal. Click injection clicks are fired after the download starts but before the first open, resulting in abnormally short click-to-install times:
| CTIT | Interpretation |
|---|---|
| < 5 seconds | Almost certainly click injection |
| 5-10 seconds | Highly suspicious |
| 10-30 seconds | Suspicious (depends on app size and connection speed) |
| 30+ seconds | Normal range for legitimate clicks |
A legitimate click happens before the user decides to install. A click injection click happens after the install has already started. The time difference is the giveaway.
def is_click_injection(click_time, install_time, download_start_time):
ctit = install_time - click_time
# Click happened after download started
if click_time > download_start_time:
return True # Definitive click injection
# Abnormally short CTIT
if ctit < 10: # seconds
return True # Very likely click injection
return False
Google Play Install Referrer API
The Google Play Install Referrer API provides two timestamps:
referrerClickTimestampSeconds: When the referrer click happened.installBeginTimestampSeconds: When the download began.
If the click timestamp is after the install begin timestamp, the click is fraudulent:
fun checkClickInjection(referrerDetails: ReferrerDetails): Boolean {
val clickTime = referrerDetails.referrerClickTimestampSeconds
val installBeginTime = referrerDetails.installBeginTimestampSeconds
// Click happened after download started = click injection
if (clickTime > installBeginTime) {
return true
}
// Click happened suspiciously close to install begin
val ctit = installBeginTime - clickTime
if (ctit < 5) {
return true
}
return false
}
Distribution Analysis
Analyze CTIT distributions across ad networks. Legitimate networks have a natural distribution curve (most installs have CTIT of 30 seconds to several hours). Click injection networks show a spike at very low CTIT values:
| Network | Mean CTIT | Median CTIT | % Under 10s |
|---|---|---|---|
| Legitimate Network A | 2.5 hours | 45 min | 0.3% |
| Legitimate Network B | 1.8 hours | 30 min | 0.5% |
| Suspicious Network C | 12 min | 4 sec | 68% |
Network C's distribution is clearly abnormal. 68% of installs have a CTIT under 10 seconds, which is not possible with legitimate clicks.
New Device Ratio
Click injection often targets specific device profiles. Check if a network has an unusual concentration of specific device models or OS versions:
def check_device_distribution(network_installs):
device_counts = Counter(i.device_model for i in network_installs)
total = len(network_installs)
for device, count in device_counts.most_common(5):
ratio = count / total
if ratio > 0.3: # One device model > 30% of installs
flag_for_review(network_installs, f"Device concentration: {device} at {ratio:.0%}")
Prevention
Use the Install Referrer API
The Play Install Referrer API provides server-validated timestamps that cannot be spoofed:
class InstallReferrerHelper(private val context: Context) {
fun getInstallReferrer(callback: (ReferrerDetails?) -> Unit) {
val client = InstallReferrerClient.newBuilder(context).build()
client.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
val details = client.installReferrer
callback(details)
} else {
callback(null)
}
client.endConnection()
}
override fun onInstallReferrerServiceDisconnected() {
callback(null)
}
})
}
}
Server-Side Validation
Validate installs on your server, not just on the device:
interface InstallEvent {
clickTime: number;
installBeginTime: number;
firstOpenTime: number;
networkId: string;
deviceId: string;
}
function validateInstall(event: InstallEvent): { valid: boolean; reason?: string } {
// Check 1: Click before install begin
if (event.clickTime > event.installBeginTime) {
return { valid: false, reason: 'click_after_download' };
}
// Check 2: CTIT minimum
const ctit = event.installBeginTime - event.clickTime;
if (ctit < 5) {
return { valid: false, reason: 'ctit_too_short' };
}
// Check 3: First open within reasonable time of install
const installToOpen = event.firstOpenTime - event.installBeginTime;
if (installToOpen < 2) {
return { valid: false, reason: 'impossible_open_time' };
}
return { valid: true };
}
Rejecting Fraudulent Installs
When you detect click injection, reject the install attribution and send a rejection postback:
async function handleFraudulentInstall(event: InstallEvent) {
// 1. Reject the attribution
await attributionDB.rejectInstall(event.deviceId, event.networkId, 'click_injection');
// 2. Re-attribute to the correct source (if there was a legitimate click before)
const legitimateClick = await findLegitimateClick(event.deviceId);
if (legitimateClick) {
await attributionDB.attribute(event.deviceId, legitimateClick.networkId);
} else {
await attributionDB.attributeOrganic(event.deviceId);
}
// 3. Send rejection postback to the fraudulent network
await sendRejectionPostback(event.networkId, event, 'click_injection');
}
Impact of Click Injection
Click injection does not generate fake installs. It steals attribution from legitimate sources (organic, other paid campaigns). The effects:
- Inflated CPI costs. You pay for installs that would have happened anyway.
- Distorted channel mix. The fraudulent network appears to perform well, so you allocate more budget to it.
- Reduced organic count. Installs that should be counted as organic are attributed to paid campaigns.
- Incorrect ROAS. Campaign performance metrics are skewed because the attributed installs are not truly driven by the ads.
Google's Protections
Google has implemented protections against click injection:
- Play Install Referrer API v2 provides
installBeginTimestampSeconds, which makes click injection detectable. - Play Protect scans apps for malicious behavior, including broadcast receiver abuse.
- Android 8.0+ restricts implicit broadcast receivers, reducing the attack surface.
Despite these protections, click injection remains prevalent because older Android devices are still in use and some users disable Play Protect.
Tolinku for Attribution
Tolinku's analytics track deep link clicks with server-validated timestamps, providing clean attribution data. Since Tolinku deep links use server-side click tracking, they are not susceptible to client-side click injection. Configure attribution tracking in the Tolinku dashboard.
For attribution fraud, see attribution fraud: detection and prevention guide. For Android deep linking, see Android App Links: complete implementation guide.
Get deep linking tips in your inbox
One email per week. No spam.