That influencer with 500K followers wants $5,000 for a sponsored post.
Sounds reasonable. Until you realize 60% of their followers are bots.
Fake followers are everywhere. One study found 45% of Instagram accounts show signs of fraudulent activity. Brands lose billions paying for audiences that don't exist.
In this tutorial, we'll build a Fake Follower Detector that:
- Analyzes any TikTok or Instagram creator
- Calculates engagement authenticity score
- Identifies red flags in audience demographics
Stop paying for ghost audiences.
The Math Behind Fake Detection
Detecting fake followers isn't magic. It's pattern recognition:
Red Flag #1: Engagement Rate
- Real accounts: 1-5% engagement rate
- Bought followers: 0.1-0.5% (followers don't engage)
- Engagement pods: 10%+ (suspiciously high)
Red Flag #2: Follower/Following Ratio
- Real influencers: High followers, low following
- Fake accounts: Often follow thousands to get follow-backs
Red Flag #3: Growth Spikes
- Real growth: Gradual, with viral spikes
- Bought followers: Sudden jumps of exactly 10K, 50K, 100K
Red Flag #4: Comment Quality
- Real: Specific, relevant comments
- Fake: "Nice! π₯" "Love this β€οΈ" "Great content!"
The Stack
- Node.js: Runtime
- SociaVault API: To fetch creator data
- OpenAI API: To analyze comment authenticity
Step 1: Setup
mkdir fake-follower-detector
cd fake-follower-detector
npm init -y
npm install axios openai dotenv
Create .env:
SOCIAVAULT_API_KEY=your_sociavault_key
OPENAI_API_KEY=your_openai_key
Step 2: Fetch Creator Profile
Let's start with TikTok. The /v1/scrape/tiktok/profile endpoint gives us followers, likes, and engagement data.
Create index.js:
require('dotenv').config();
const axios = require('axios');
const OpenAI = require('openai');
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const SOCIAVAULT_BASE = 'https://api.sociavault.com';
async function getTikTokProfile(handle) {
console.log(`π₯ Fetching @${handle}'s profile...`);
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/tiktok/profile`, {
params: { handle },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
const data = response.data.data;
return {
handle: data.uniqueId || data.handle,
displayName: data.nickname || data.displayName,
followers: data.followerCount || data.fans || 0,
following: data.followingCount || data.following || 0,
likes: data.heartCount || data.heart || data.likes || 0,
videos: data.videoCount || data.video || 0,
verified: data.verified || false,
bio: data.signature || data.bio || ''
};
} catch (error) {
console.error('Error fetching profile:', error.message);
return null;
}
}
Step 3: Get Recent Videos with Engagement
We need actual engagement data from their content:
async function getRecentVideos(handle) {
console.log(`π₯ Fetching recent videos...`);
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/tiktok/videos`, {
params: { handle, amount: 30 },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
const videos = response.data.data || [];
return videos.map(v => ({
id: v.id,
description: v.desc || v.description,
likes: v.diggCount || v.stats?.diggCount || 0,
comments: v.commentCount || v.stats?.commentCount || 0,
shares: v.shareCount || v.stats?.shareCount || 0,
views: v.playCount || v.stats?.playCount || 0,
created: v.createTime
}));
} catch (error) {
console.error('Error fetching videos:', error.message);
return [];
}
}
Step 4: Calculate Engagement Metrics
This is where the magic happens:
function calculateEngagementMetrics(profile, videos) {
// Basic engagement rate (avg across videos)
const engagementRates = videos.map(v => {
if (v.views === 0) return 0;
return ((v.likes + v.comments + v.shares) / v.views) * 100;
});
const avgEngagementRate = engagementRates.reduce((a, b) => a + b, 0) / engagementRates.length;
// Follower to engagement ratio
const totalEngagement = videos.reduce((sum, v) => sum + v.likes + v.comments, 0);
const engagementPerFollower = totalEngagement / profile.followers;
// Follower/Following ratio
const followerRatio = profile.following > 0
? profile.followers / profile.following
: profile.followers;
// View consistency (standard deviation)
const views = videos.map(v => v.views);
const avgViews = views.reduce((a, b) => a + b, 0) / views.length;
const variance = views.reduce((sum, v) => sum + Math.pow(v - avgViews, 2), 0) / views.length;
const viewStdDev = Math.sqrt(variance);
const viewConsistency = avgViews > 0 ? (viewStdDev / avgViews) : 0;
// Comments to likes ratio (bots like but don't comment)
const totalLikes = videos.reduce((sum, v) => sum + v.likes, 0);
const totalComments = videos.reduce((sum, v) => sum + v.comments, 0);
const commentToLikeRatio = totalLikes > 0 ? totalComments / totalLikes : 0;
return {
avgEngagementRate: avgEngagementRate.toFixed(2),
engagementPerFollower: engagementPerFollower.toFixed(4),
followerRatio: followerRatio.toFixed(1),
viewConsistency: viewConsistency.toFixed(2),
commentToLikeRatio: commentToLikeRatio.toFixed(3),
avgViews: Math.round(avgViews),
totalVideosAnalyzed: videos.length
};
}
Step 5: Fetch and Analyze Comments
Bot comments have patterns. Let's detect them:
async function getVideoComments(videoUrl) {
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/tiktok/comments`, {
params: { url: videoUrl },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
return response.data.data || [];
} catch (error) {
console.error('Error fetching comments:', error.message);
return [];
}
}
async function analyzeCommentAuthenticity(comments) {
if (comments.length === 0) return { score: 0, analysis: 'No comments to analyze' };
console.log(`π€ Analyzing ${comments.length} comments...`);
const sample = comments.slice(0, 50);
const prompt = `
Analyze these TikTok comments for signs of fake/bot engagement.
Bot comment patterns:
- Generic praise: "Nice!" "Love this" "Great content" "π₯π₯π₯"
- Unrelated to video content
- Repetitive emoji-only comments
- Promotional spam with links
- Very short, low-effort comments
Real comment patterns:
- Specific references to video content
- Questions about the topic
- Personal stories or opinions
- Genuine reactions with context
Return JSON:
{
"authenticityScore": 0-100 (100 = all real, 0 = all fake),
"suspiciousComments": number of likely bot comments,
"realComments": number of likely genuine comments,
"redFlags": ["list of specific concerns"],
"examples": {
"suspicious": ["2 example bot-like comments"],
"genuine": ["2 example real comments"]
},
"verdict": "one sentence assessment"
}
Comments to analyze:
${JSON.stringify(sample.map(c => c.text || c.comment))}
`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' }
});
return JSON.parse(completion.choices[0].message.content);
}
Step 6: The Authenticity Score Algorithm
Let's combine everything into a final score:
function calculateAuthenticityScore(metrics, commentAnalysis) {
let score = 100;
const redFlags = [];
// Engagement rate check
const engRate = parseFloat(metrics.avgEngagementRate);
if (engRate < 0.5) {
score -= 25;
redFlags.push(`Very low engagement rate (${engRate}%) - possible fake followers`);
} else if (engRate > 15) {
score -= 15;
redFlags.push(`Unusually high engagement rate (${engRate}%) - possible engagement pods`);
}
// Follower ratio check
const ratio = parseFloat(metrics.followerRatio);
if (ratio < 2) {
score -= 10;
redFlags.push(`Low follower/following ratio (${ratio}x) - follow-for-follow pattern`);
}
// Comment to like ratio
const ctr = parseFloat(metrics.commentToLikeRatio);
if (ctr < 0.01) {
score -= 20;
redFlags.push(`Very few comments relative to likes (${(ctr * 100).toFixed(1)}%) - bots like but don't comment`);
}
// View consistency (suspicious if too consistent)
const consistency = parseFloat(metrics.viewConsistency);
if (consistency < 0.3) {
score -= 10;
redFlags.push(`Suspiciously consistent view counts - possible view botting`);
}
// Comment authenticity (from AI analysis)
if (commentAnalysis.authenticityScore) {
const commentScore = commentAnalysis.authenticityScore;
if (commentScore < 50) {
score -= 25;
redFlags.push(`${100 - commentScore}% of comments appear to be bot-generated`);
} else if (commentScore < 70) {
score -= 10;
redFlags.push(`${100 - commentScore}% of comments show bot-like patterns`);
}
}
// Add red flags from AI
if (commentAnalysis.redFlags) {
redFlags.push(...commentAnalysis.redFlags);
}
return {
score: Math.max(0, score),
grade: getGrade(Math.max(0, score)),
redFlags
};
}
function getGrade(score) {
if (score >= 90) return { letter: 'A', label: 'Highly Authentic', emoji: 'β
' };
if (score >= 75) return { letter: 'B', label: 'Mostly Authentic', emoji: 'π' };
if (score >= 60) return { letter: 'C', label: 'Some Concerns', emoji: 'β οΈ' };
if (score >= 40) return { letter: 'D', label: 'Likely Inflated', emoji: 'π¨' };
return { letter: 'F', label: 'Highly Suspicious', emoji: 'β' };
}
Step 7: The Full Analysis
Tie it all together:
async function analyzeCreator(handle) {
console.log('\nπ FAKE FOLLOWER DETECTOR\n');
console.log('βββββββββββββββββββββββββββββββββββββββ\n');
// 1. Get profile
const profile = await getTikTokProfile(handle);
if (!profile) {
console.log('Could not fetch profile.');
return;
}
console.log(`π€ @${profile.handle} ${profile.verified ? 'β' : ''}`);
console.log(` ${profile.displayName}`);
console.log(` ${profile.followers.toLocaleString()} followers | ${profile.following.toLocaleString()} following`);
console.log(` ${profile.likes.toLocaleString()} total likes | ${profile.videos} videos\n`);
// 2. Get videos
const videos = await getRecentVideos(handle);
if (videos.length === 0) {
console.log('Could not fetch videos.');
return;
}
// 3. Calculate metrics
const metrics = calculateEngagementMetrics(profile, videos);
console.log('π ENGAGEMENT METRICS');
console.log('βββββββββββββββββββββββββββββββββββββββ');
console.log(` Avg Engagement Rate: ${metrics.avgEngagementRate}%`);
console.log(` Avg Views per Video: ${metrics.avgViews.toLocaleString()}`);
console.log(` Follower Ratio: ${metrics.followerRatio}x`);
console.log(` Comment/Like Ratio: ${(parseFloat(metrics.commentToLikeRatio) * 100).toFixed(1)}%`);
console.log(` View Consistency: ${metrics.viewConsistency}`);
console.log(` Videos Analyzed: ${metrics.totalVideosAnalyzed}\n`);
// 4. Analyze comments from top video
const topVideo = videos.reduce((max, v) => v.views > max.views ? v : max, videos[0]);
const videoUrl = `https://www.tiktok.com/@${handle}/video/${topVideo.id}`;
const comments = await getVideoComments(videoUrl);
const commentAnalysis = await analyzeCommentAuthenticity(comments);
console.log('π¬ COMMENT ANALYSIS');
console.log('βββββββββββββββββββββββββββββββββββββββ');
console.log(` Authenticity Score: ${commentAnalysis.authenticityScore}/100`);
console.log(` Genuine Comments: ~${commentAnalysis.realComments}`);
console.log(` Suspicious Comments: ~${commentAnalysis.suspiciousComments}`);
console.log(` Verdict: ${commentAnalysis.verdict}\n`);
// 5. Calculate final score
const authenticity = calculateAuthenticityScore(metrics, commentAnalysis);
console.log('βββββββββββββββββββββββββββββββββββββββ');
console.log('π― AUTHENTICITY VERDICT');
console.log('βββββββββββββββββββββββββββββββββββββββ\n');
console.log(` ${authenticity.grade.emoji} GRADE: ${authenticity.grade.letter} - ${authenticity.grade.label}`);
console.log(` π SCORE: ${authenticity.score}/100\n`);
if (authenticity.redFlags.length > 0) {
console.log(' π© RED FLAGS:');
authenticity.redFlags.forEach((flag, i) => {
console.log(` ${i + 1}. ${flag}`);
});
} else {
console.log(' β
No significant red flags detected');
}
// 6. Recommendation
console.log('\nπ RECOMMENDATION:');
if (authenticity.score >= 75) {
console.log(' This creator appears legitimate. Safe to collaborate.');
} else if (authenticity.score >= 50) {
console.log(' Proceed with caution. Ask for media kit and past campaign results.');
} else {
console.log(' High risk of fake engagement. Consider other creators.');
}
return { profile, metrics, commentAnalysis, authenticity };
}
Step 8: Run It
async function main() {
const handle = process.argv[2] || 'charlidamelio';
await analyzeCreator(handle);
}
main();
Run with:
node index.js khaby.lame
Sample Output
π FAKE FOLLOWER DETECTOR
βββββββββββββββββββββββββββββββββββββββ
π₯ Fetching @khaby.lame's profile...
π₯ Fetching recent videos...
π€ @khaby.lame β
Khabane lame
162,500,000 followers | 78 following
2,400,000,000 total likes | 1,200 videos
π ENGAGEMENT METRICS
βββββββββββββββββββββββββββββββββββββββ
Avg Engagement Rate: 3.2%
Avg Views per Video: 45,000,000
Follower Ratio: 2,083,333.3x
Comment/Like Ratio: 2.8%
View Consistency: 1.4
Videos Analyzed: 30
π€ Analyzing 50 comments...
π¬ COMMENT ANALYSIS
βββββββββββββββββββββββββββββββββββββββ
Authenticity Score: 78/100
Genuine Comments: ~35
Suspicious Comments: ~15
Verdict: Mostly authentic engagement with some generic comments
βββββββββββββββββββββββββββββββββββββββ
π― AUTHENTICITY VERDICT
βββββββββββββββββββββββββββββββββββββββ
β
GRADE: A - Highly Authentic
π SCORE: 92/100
β
No significant red flags detected
π RECOMMENDATION:
This creator appears legitimate. Safe to collaborate.
Adding Instagram Support
The same logic works for Instagram. Just swap the endpoints:
async function getInstagramProfile(handle) {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/instagram/profile`, {
params: { handle },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
const data = response.data.data;
return {
handle: data.username,
displayName: data.full_name,
followers: data.follower_count,
following: data.following_count,
posts: data.media_count,
verified: data.is_verified,
bio: data.biography
};
}
async function getInstagramPosts(handle) {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/instagram/posts`, {
params: { handle },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
return response.data.data || [];
}
Bulk Analysis for Agencies
If you're vetting multiple creators:
async function analyzeMultipleCreators(handles) {
const results = [];
for (const handle of handles) {
console.log(`\n${'β'.repeat(50)}`);
const result = await analyzeCreator(handle);
results.push({ handle, ...result });
// Be nice to the API
await new Promise(r => setTimeout(r, 2000));
}
// Summary table
console.log('\n\nπ SUMMARY');
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('Handle'.padEnd(20) + 'Score'.padEnd(10) + 'Grade'.padEnd(10) + 'Followers');
console.log('β'.repeat(55));
results
.sort((a, b) => b.authenticity.score - a.authenticity.score)
.forEach(r => {
console.log(
`@${r.handle}`.padEnd(20) +
`${r.authenticity.score}/100`.padEnd(10) +
r.authenticity.grade.letter.padEnd(10) +
r.profile.followers.toLocaleString()
);
});
return results;
}
// Usage
analyzeMultipleCreators([
'charlidamelio',
'khaby.lame',
'addisonre',
'suspicious_account_123'
]);
What This Tool Would Cost Elsewhere
Influencer authenticity tools charge:
- HypeAuditor: $299/month (limited reports)
- Social Blade Pro: $3.99/month (basic stats only)
- Influencer Marketing Hub: $500+/month (full vetting)
Your version:
- SociaVault credits: ~$0.05 per analysis
- OpenAI API: ~$0.01 per analysis
- Total: ~$0.06 per creator
At that price, you can afford to vet every influencer before paying them.
Get Started
- Get your SociaVault API Key
- Add your OpenAI key
- Start exposing fake influencers
Your marketing budget deserves real audiences.
The fake follower industry is a $1.3 billion problem. Be part of the solution.
Top comments (0)