{"id":1734,"date":"2026-07-13T09:00:00","date_gmt":"2026-07-13T14:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1734"},"modified":"2026-03-07T03:50:05","modified_gmt":"2026-03-07T08:50:05","slug":"click-injection-detection","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/click-injection-detection\/","title":{"rendered":"Click Injection Detection for Mobile Apps"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Click injection is a mobile ad fraud technique specific to Android. A malicious app on the user&#39;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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This guide explains how click injection works, how to detect it, and how to prevent it. For attribution fraud broadly, see <a href=\"https:\/\/tolinku.com\/blog\/attribution-fraud-detection\/\">attribution fraud: detection and prevention guide<\/a>. For mobile attribution, see <a href=\"https:\/\/tolinku.com\/blog\/mobile-attribution-developers-guide\/\">mobile attribution: a developer&#39;s guide<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Click Injection Works<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">The Android Broadcast<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">On Android, the system sends a <a href=\"https:\/\/developer.android.com\/google\/play\/installreferrer\" rel=\"nofollow noopener\" target=\"_blank\"><code>com.android.vending.INSTALL_REFERRER<\/code><\/a> broadcast when an app is installed from the Google Play Store. Before Google restricted this broadcast, any app could listen for it:<\/p>\n\n\n\n<pre><code>1. User sees a legitimate ad and decides to install the app\n2. User downloads from Google Play Store\n3. Android broadcasts INSTALL_REFERRER\n4. Malicious app on the device intercepts the broadcast\n5. Malicious app fires a fake ad click with matching device ID\n6. App opens for the first time\n7. Attribution provider sees the fake click (most recent) and attributes the install to it\n8. Fraudulent network gets paid for the install\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why It Only Affects Android<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Detection Signals<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Click-to-Install Time (CTIT)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">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:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>CTIT<\/th>\n<th>Interpretation<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>&lt; 5 seconds<\/td>\n<td>Almost certainly click injection<\/td>\n<\/tr>\n<tr>\n<td>5-10 seconds<\/td>\n<td>Highly suspicious<\/td>\n<\/tr>\n<tr>\n<td>10-30 seconds<\/td>\n<td>Suspicious (depends on app size and connection speed)<\/td>\n<\/tr>\n<tr>\n<td>30+ seconds<\/td>\n<td>Normal range for legitimate clicks<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<pre><code class=\"language-python\">def is_click_injection(click_time, install_time, download_start_time):\n    ctit = install_time - click_time\n\n    # Click happened after download started\n    if click_time &gt; download_start_time:\n        return True  # Definitive click injection\n\n    # Abnormally short CTIT\n    if ctit &lt; 10:  # seconds\n        return True  # Very likely click injection\n\n    return False\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Google Play Install Referrer API<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/developer.android.com\/google\/play\/installreferrer\/library\" rel=\"nofollow noopener\" target=\"_blank\">Google Play Install Referrer API<\/a> provides two timestamps:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>referrerClickTimestampSeconds<\/code>: When the referrer click happened.<\/li>\n<li><code>installBeginTimestampSeconds<\/code>: When the download began.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If the click timestamp is after the install begin timestamp, the click is fraudulent:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">fun checkClickInjection(referrerDetails: ReferrerDetails): Boolean {\n    val clickTime = referrerDetails.referrerClickTimestampSeconds\n    val installBeginTime = referrerDetails.installBeginTimestampSeconds\n\n    \/\/ Click happened after download started = click injection\n    if (clickTime &gt; installBeginTime) {\n        return true\n    }\n\n    \/\/ Click happened suspiciously close to install begin\n    val ctit = installBeginTime - clickTime\n    if (ctit &lt; 5) {\n        return true\n    }\n\n    return false\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Distribution Analysis<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">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:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Network<\/th>\n<th>Mean CTIT<\/th>\n<th>Median CTIT<\/th>\n<th>% Under 10s<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Legitimate Network A<\/td>\n<td>2.5 hours<\/td>\n<td>45 min<\/td>\n<td>0.3%<\/td>\n<\/tr>\n<tr>\n<td>Legitimate Network B<\/td>\n<td>1.8 hours<\/td>\n<td>30 min<\/td>\n<td>0.5%<\/td>\n<\/tr>\n<tr>\n<td>Suspicious Network C<\/td>\n<td>12 min<\/td>\n<td>4 sec<\/td>\n<td>68%<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Network C&#39;s distribution is clearly abnormal. 68% of installs have a CTIT under 10 seconds, which is not possible with legitimate clicks.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">New Device Ratio<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Click injection often targets specific device profiles. Check if a network has an unusual concentration of specific device models or OS versions:<\/p>\n\n\n\n<pre><code class=\"language-python\">def check_device_distribution(network_installs):\n    device_counts = Counter(i.device_model for i in network_installs)\n    total = len(network_installs)\n\n    for device, count in device_counts.most_common(5):\n        ratio = count \/ total\n        if ratio &gt; 0.3:  # One device model &gt; 30% of installs\n            flag_for_review(network_installs, f&quot;Device concentration: {device} at {ratio:.0%}&quot;)\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Prevention<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Use the Install Referrer API<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/developer.android.com\/google\/play\/installreferrer\/library\" rel=\"nofollow noopener\" target=\"_blank\">Play Install Referrer API<\/a> provides server-validated timestamps that cannot be spoofed:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">class InstallReferrerHelper(private val context: Context) {\n    fun getInstallReferrer(callback: (ReferrerDetails?) -&gt; Unit) {\n        val client = InstallReferrerClient.newBuilder(context).build()\n\n        client.startConnection(object : InstallReferrerStateListener {\n            override fun onInstallReferrerSetupFinished(responseCode: Int) {\n                if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {\n                    val details = client.installReferrer\n                    callback(details)\n                } else {\n                    callback(null)\n                }\n                client.endConnection()\n            }\n\n            override fun onInstallReferrerServiceDisconnected() {\n                callback(null)\n            }\n        })\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Server-Side Validation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Validate installs on your server, not just on the device:<\/p>\n\n\n\n<pre><code class=\"language-typescript\">interface InstallEvent {\n  clickTime: number;\n  installBeginTime: number;\n  firstOpenTime: number;\n  networkId: string;\n  deviceId: string;\n}\n\nfunction validateInstall(event: InstallEvent): { valid: boolean; reason?: string } {\n  \/\/ Check 1: Click before install begin\n  if (event.clickTime &gt; event.installBeginTime) {\n    return { valid: false, reason: &#39;click_after_download&#39; };\n  }\n\n  \/\/ Check 2: CTIT minimum\n  const ctit = event.installBeginTime - event.clickTime;\n  if (ctit &lt; 5) {\n    return { valid: false, reason: &#39;ctit_too_short&#39; };\n  }\n\n  \/\/ Check 3: First open within reasonable time of install\n  const installToOpen = event.firstOpenTime - event.installBeginTime;\n  if (installToOpen &lt; 2) {\n    return { valid: false, reason: &#39;impossible_open_time&#39; };\n  }\n\n  return { valid: true };\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Rejecting Fraudulent Installs<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When you detect click injection, reject the install attribution and send a rejection postback:<\/p>\n\n\n\n<pre><code class=\"language-typescript\">async function handleFraudulentInstall(event: InstallEvent) {\n  \/\/ 1. Reject the attribution\n  await attributionDB.rejectInstall(event.deviceId, event.networkId, &#39;click_injection&#39;);\n\n  \/\/ 2. Re-attribute to the correct source (if there was a legitimate click before)\n  const legitimateClick = await findLegitimateClick(event.deviceId);\n  if (legitimateClick) {\n    await attributionDB.attribute(event.deviceId, legitimateClick.networkId);\n  } else {\n    await attributionDB.attributeOrganic(event.deviceId);\n  }\n\n  \/\/ 3. Send rejection postback to the fraudulent network\n  await sendRejectionPostback(event.networkId, event, &#39;click_injection&#39;);\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Impact of Click Injection<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Click injection does not generate fake installs. It steals attribution from legitimate sources (organic, other paid campaigns). The effects:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Inflated CPI costs.<\/strong> You pay for installs that would have happened anyway.<\/li>\n<li><strong>Distorted channel mix.<\/strong> The fraudulent network appears to perform well, so you allocate more budget to it.<\/li>\n<li><strong>Reduced organic count.<\/strong> Installs that should be counted as organic are attributed to paid campaigns.<\/li>\n<li><strong>Incorrect ROAS.<\/strong> Campaign performance metrics are skewed because the attributed installs are not truly driven by the ads.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Google&#39;s Protections<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Google has implemented protections against click injection:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Play Install Referrer API v2<\/strong> provides <code>installBeginTimestampSeconds<\/code>, which makes click injection detectable.<\/li>\n<li><strong>Play Protect<\/strong> scans apps for malicious behavior, including broadcast receiver abuse.<\/li>\n<li><strong>Android 8.0+<\/strong> restricts implicit broadcast receivers, reducing the attack surface.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Despite these protections, click injection remains prevalent because older Android devices are still in use and some users disable Play Protect.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku for Attribution<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/analytics\">Tolinku&#39;s analytics<\/a> 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 <a href=\"https:\/\/tolinku.com\/docs\/concepts\/attribution\/\">Tolinku dashboard<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For attribution fraud, see <a href=\"https:\/\/tolinku.com\/blog\/attribution-fraud-detection\/\">attribution fraud: detection and prevention guide<\/a>. For Android deep linking, see <a href=\"https:\/\/tolinku.com\/blog\/android-app-links-complete-guide\/\">Android App Links: complete implementation guide<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Detect and prevent click injection fraud on Android. Understand how it works, identify patterns, and implement protection measures.<\/p>\n","protected":false},"author":2,"featured_media":1733,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Click Injection Detection for Mobile Apps","rank_math_description":"Detect and prevent click injection fraud on Android. Understand how it works, identify patterns, and implement protection.","rank_math_focus_keyword":"click injection detection","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-click-injection-detection.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-click-injection-detection.png","footnotes":""},"categories":[14],"tags":[37,25,28,20,126,518,69,93],"class_list":["post-1734","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-analytics","tag-android","tag-attribution","tag-deep-linking","tag-fraud-detection","tag-mobile-advertising","tag-mobile-development","tag-security"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1734","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1734"}],"version-history":[{"count":3,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1734\/revisions"}],"predecessor-version":[{"id":2691,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1734\/revisions\/2691"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1733"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1734"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1734"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1734"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}