How to Detect Fake Followers & Vet Influencers
Influencer fraud costs brands billions every year.
Fake followers. Bot engagement. Inflated metrics. You've probably seen itβprofiles with millions of followers but suspiciously low engagement.
This guide shows you how to spot fakes using data. No guesswork. Just metrics.
The Scale of the Problem
Industry estimates:
- 15-25% of influencer followers are fake
- $1.3 billion lost to influencer fraud annually
- 50%+ of engagement on some accounts is artificial
You need to verify before you spend.
Red Flags to Watch For
1. Suspicious Follower-to-Engagement Ratio
Real accounts typically see:
- TikTok: 3-9% engagement rate
- Instagram: 1-5% engagement rate
- YouTube: 2-8% view rate (vs subscribers)
- Twitter: 0.5-3% engagement rate
Below 1% on Instagram or TikTok? Red flag.
2. Sudden Follower Spikes
Organic growth is gradual. Purchased followers show up as sudden jumps.
Real growth: π steady upward curve
Fake growth: πππ spikes followed by drops
3. Generic Comments
Bot comments look like:
- "Nice! π₯"
- "Love this β€οΈ"
- "Great content!"
- Random emojis with no context
Real comments reference specific content.
4. Ghost Followers
Accounts that follow but never engage:
- No profile picture
- No posts
- Generic usernames (user28374839)
- Created recently
Data-Driven Vetting
Calculate Engagement Rate
function calculateEngagementRate(profile, posts) {
const totalEngagement = posts.reduce((sum, post) => {
return sum + (post.like_count || 0) + (post.comment_count || 0);
}, 0);
const avgEngagement = totalEngagement / posts.length;
const engagementRate = (avgEngagement / profile.followers) * 100;
return {
engagementRate: engagementRate.toFixed(2),
avgLikes: (posts.reduce((s, p) => s + p.like_count, 0) / posts.length).toFixed(0),
avgComments: (posts.reduce((s, p) => s + p.comment_count, 0) / posts.length).toFixed(0),
isHealthy: engagementRate > 1 && engagementRate < 15
};
}
Analyze Engagement Consistency
Fake engagement is often inconsistent:
function analyzeConsistency(posts) {
const engagements = posts.map(p => p.like_count + p.comment_count);
const mean = engagements.reduce((a, b) => a + b, 0) / engagements.length;
const variance = engagements.reduce((sum, e) => sum + Math.pow(e - mean, 2), 0) / engagements.length;
const stdDev = Math.sqrt(variance);
const coefficientOfVariation = (stdDev / mean) * 100;
return {
mean: mean.toFixed(0),
stdDev: stdDev.toFixed(0),
cv: coefficientOfVariation.toFixed(1),
// CV > 100% suggests inconsistent (possibly fake) engagement
isConsistent: coefficientOfVariation < 80
};
}
Check Comment Quality
const GENERIC_COMMENTS = [
'nice', 'love it', 'great', 'amazing', 'beautiful', 'wow',
'fire', 'awesome', 'π₯', 'β€οΈ', 'π', 'π―'
];
function analyzeCommentQuality(comments) {
const genericCount = comments.filter(c => {
const text = c.text.toLowerCase().trim();
return text.length < 15 || GENERIC_COMMENTS.some(g => text.includes(g));
}).length;
const genericPercentage = (genericCount / comments.length) * 100;
// Check for duplicate comments
const uniqueComments = new Set(comments.map(c => c.text.toLowerCase()));
const duplicatePercentage = ((comments.length - uniqueComments.size) / comments.length) * 100;
return {
genericPercentage: genericPercentage.toFixed(1),
duplicatePercentage: duplicatePercentage.toFixed(1),
avgCommentLength: (comments.reduce((s, c) => s + c.text.length, 0) / comments.length).toFixed(0),
// High generic % or duplicates suggests bot activity
isAuthentic: genericPercentage < 40 && duplicatePercentage < 10
};
}
Complete Vetting System
Here's a full implementation:
const API_KEY = process.env.SOCIAVAULT_API_KEY;
async function vetInfluencer(platform, username) {
// Fetch profile
const profileRes = await fetch(
`https://api.sociavault.com/v1/scrape/${platform}/profile?username=${username}`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);
const profile = (await profileRes.json()).data;
// Fetch posts
const postsRes = await fetch(
`https://api.sociavault.com/v1/scrape/${platform}/posts?username=${username}&count=30`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);
const posts = (await postsRes.json()).data.posts || (await postsRes.json()).data.videos;
// Fetch comments from top posts
const topPosts = posts.sort((a, b) => b.like_count - a.like_count).slice(0, 5);
const allComments = [];
for (const post of topPosts) {
const commentsRes = await fetch(
`https://api.sociavault.com/v1/scrape/${platform}/comments?postId=${post.id}&count=50`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);
const comments = (await commentsRes.json()).data.comments || [];
allComments.push(...comments);
await new Promise(r => setTimeout(r, 500)); // Rate limit
}
// Run analyses
const engagement = calculateEngagementRate(profile, posts);
const consistency = analyzeConsistency(posts);
const commentQuality = analyzeCommentQuality(allComments);
// Calculate trust score (0-100)
let trustScore = 100;
// Engagement rate penalties
if (engagement.engagementRate < 1) trustScore -= 25;
if (engagement.engagementRate > 15) trustScore -= 15; // Suspiciously high
// Consistency penalties
if (!consistency.isConsistent) trustScore -= 20;
// Comment quality penalties
if (!commentQuality.isAuthentic) trustScore -= 25;
if (parseFloat(commentQuality.duplicatePercentage) > 20) trustScore -= 15;
// Follower ratio checks
const followerRatio = profile.followers / (profile.following || 1);
if (followerRatio < 0.1) trustScore -= 10; // Following way more than followers
return {
username,
platform,
followers: profile.followers,
trustScore: Math.max(0, trustScore),
verdict: trustScore >= 70 ? 'LIKELY AUTHENTIC' :
trustScore >= 50 ? 'NEEDS REVIEW' : 'HIGH RISK',
metrics: {
engagement,
consistency,
commentQuality,
followerRatio: followerRatio.toFixed(2)
},
redFlags: generateRedFlags(engagement, consistency, commentQuality)
};
}
function generateRedFlags(engagement, consistency, comments) {
const flags = [];
if (engagement.engagementRate < 1) {
flags.push('Very low engagement rate - possible fake followers');
}
if (engagement.engagementRate > 15) {
flags.push('Unusually high engagement - possible engagement pods or bots');
}
if (!consistency.isConsistent) {
flags.push('Inconsistent engagement - varies too much between posts');
}
if (parseFloat(comments.genericPercentage) > 50) {
flags.push('High percentage of generic comments - possible bot activity');
}
if (parseFloat(comments.duplicatePercentage) > 10) {
flags.push('Many duplicate comments - likely bot farm');
}
return flags;
}
Python Implementation
import os
import requests
import statistics
from dataclasses import dataclass
from typing import List, Dict
API_KEY = os.getenv('SOCIAVAULT_API_KEY')
API_BASE = 'https://api.sociavault.com/v1/scrape'
@dataclass
class VettingResult:
username: str
platform: str
followers: int
trust_score: int
verdict: str
engagement_rate: float
red_flags: List[str]
def vet_influencer(platform: str, username: str) -> VettingResult:
headers = {'Authorization': f'Bearer {API_KEY}'}
# Fetch profile
profile_res = requests.get(
f'{API_BASE}/{platform}/profile',
params={'username': username},
headers=headers
)
profile = profile_res.json()['data']
# Fetch posts
endpoint = 'videos' if platform == 'tiktok' else 'posts'
posts_res = requests.get(
f'{API_BASE}/{platform}/{endpoint}',
params={'username': username, 'count': 30},
headers=headers
)
posts = posts_res.json()['data'].get('posts') or posts_res.json()['data'].get('videos', [])
# Calculate engagement
followers = profile.get('follower_count') or profile.get('followers', 0)
total_engagement = sum(p.get('like_count', 0) + p.get('comment_count', 0) for p in posts)
avg_engagement = total_engagement / len(posts) if posts else 0
engagement_rate = (avg_engagement / followers * 100) if followers > 0 else 0
# Calculate consistency
engagements = [p.get('like_count', 0) + p.get('comment_count', 0) for p in posts]
cv = (statistics.stdev(engagements) / statistics.mean(engagements) * 100) if len(engagements) > 1 and statistics.mean(engagements) > 0 else 0
# Generate score and flags
trust_score = 100
red_flags = []
if engagement_rate < 1:
trust_score -= 25
red_flags.append('Very low engagement rate')
elif engagement_rate > 15:
trust_score -= 15
red_flags.append('Suspiciously high engagement')
if cv > 80:
trust_score -= 20
red_flags.append('Inconsistent engagement patterns')
if followers < 1000:
trust_score -= 10
red_flags.append('Very small following')
verdict = 'LIKELY AUTHENTIC' if trust_score >= 70 else \
'NEEDS REVIEW' if trust_score >= 50 else 'HIGH RISK'
return VettingResult(
username=username,
platform=platform,
followers=followers,
trust_score=max(0, trust_score),
verdict=verdict,
engagement_rate=round(engagement_rate, 2),
red_flags=red_flags
)
# Usage
result = vet_influencer('instagram', 'suspicious_account')
print(f"""
Username: @{result.username}
Followers: {result.followers:,}
Engagement: {result.engagement_rate}%
Trust Score: {result.trust_score}/100
Verdict: {result.verdict}
Red Flags: {', '.join(result.red_flags) if result.red_flags else 'None'}
""")
Batch Vetting
Vet multiple influencers at once:
async function batchVet(influencers) {
const results = [];
for (const { platform, username } of influencers) {
console.log(`Vetting ${platform}/${username}...`);
try {
const result = await vetInfluencer(platform, username);
results.push(result);
} catch (error) {
results.push({
username,
platform,
error: error.message
});
}
// Rate limiting
await new Promise(r => setTimeout(r, 1000));
}
// Sort by trust score
return results.sort((a, b) => (b.trustScore || 0) - (a.trustScore || 0));
}
// Usage
const candidates = [
{ platform: 'instagram', username: 'creator1' },
{ platform: 'tiktok', username: 'creator2' },
{ platform: 'youtube', username: 'creator3' }
];
const vetted = await batchVet(candidates);
vetted.forEach(r => {
console.log(`${r.username}: ${r.verdict} (${r.trustScore}/100)`);
});
Advanced Detection
Follower Growth Analysis
async function analyzeGrowth(platform, username, days = 30) {
// This requires historical data - either from your database or the API
const history = await getHistoricalData(platform, username, days);
// Detect sudden spikes
const growthRates = [];
for (let i = 1; i < history.length; i++) {
const growth = (history[i].followers - history[i-1].followers) / history[i-1].followers * 100;
growthRates.push(growth);
}
const avgGrowth = growthRates.reduce((a, b) => a + b, 0) / growthRates.length;
const spikes = growthRates.filter(g => g > avgGrowth * 3); // 3x average = spike
return {
averageDailyGrowth: avgGrowth.toFixed(2),
suspiciousSpikes: spikes.length,
isOrganic: spikes.length < 2
};
}
Audience Quality Analysis
async function analyzeAudience(platform, username) {
const followersRes = await fetch(
`https://api.sociavault.com/v1/scrape/${platform}/followers?username=${username}&count=200`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);
const followers = (await followersRes.json()).data.followers;
let botScore = 0;
for (const follower of followers) {
// Check for bot indicators
if (!follower.avatar_url) botScore++;
if (follower.posts_count === 0) botScore++;
if (follower.username.match(/\d{5,}/)) botScore++; // Long number strings
if (follower.followers < 10 && follower.following > 1000) botScore++;
}
const botPercentage = (botScore / (followers.length * 4)) * 100;
return {
sampleSize: followers.length,
estimatedBotPercentage: botPercentage.toFixed(1),
audienceQuality: botPercentage < 15 ? 'GOOD' :
botPercentage < 30 ? 'MODERATE' : 'POOR'
};
}
Vetting Checklist
Use this checklist for manual review:
Profile
- Bio looks professional/authentic
- Profile picture is real (reverse image search)
- Posting history is consistent
- Account age > 1 year
Engagement
- Engagement rate 1-10%
- Comments are specific/meaningful
- Like/comment ratio is normal (10:1 to 50:1)
- No obvious engagement pods
Followers
- Follower growth is gradual
- Followers have real profiles
- Geographic distribution makes sense
- Follower/following ratio is reasonable
Content
- Content quality matches follower count
- Posting frequency is consistent
- Previous brand partnerships look legitimate
- No history of scandals/issues
Ready to vet influencers at scale?
Get your API key at sociavault.com with 50 free credits.
Related:
Found this helpful?
Share it with others who might benefit
Ready to Try SociaVault?
Start extracting social media data with our powerful API. No credit card required.