The 2026 FIFA World Cup starts June 11 across the US, Canada, and Mexico. 48 teams for the first time ever. More matches, more upsets, more chaos -- and a ridiculous amount of social media activity.
Headlines will tell you who won. Social data tells you who the internet is actually talking about, which fan bases are the loudest, and which underdog is going viral before the pundits catch on.
I built a tracker that pulls real-time social intelligence on World Cup teams using LunarCrush's API. It processes 50M+ social posts per hour across X, Reddit, TikTok, YouTube, Instagram, and 10K+ news sources.
Here's how to build it yourself.
GitHub repo: JoeVezzani/world-cup-tracker
What we're building
A Node.js script that:
- Tracks social volume, sentiment, and engagement for any World Cup team
- Compares teams head-to-head (USA vs Mexico, Argentina vs Brazil, etc.)
- Ranks all 48 teams by social buzz to find who's trending
- Surfaces the most viral posts driving each team's conversation
Setup
mkdir world-cup-tracker && cd world-cup-tracker
npm init -y
Grab an API key at lunarcrush.com/developers.
The core functions
Two endpoints do all the heavy lifting -- one for metrics, one for posts:
const API_KEY = process.env.LUNARCRUSH_API_KEY;
const BASE = "https://lunarcrush.com/api4/public/topic";
async function getTopicData(keyword) {
const res = await fetch(
`${BASE}/${encodeURIComponent(keyword)}/v1`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
return res.json();
}
async function getTopicPosts(keyword) {
const res = await fetch(
`${BASE}/${encodeURIComponent(keyword)}/posts/v1`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
return res.json();
}
Track a single team
Start simple. Pull the social pulse on any team:
async function teamReport(team) {
const topic = await getTopicData(team);
const d = topic.data || {};
console.log(`\nSOCIAL REPORT: ${team.toUpperCase()}`);
console.log("=".repeat(50));
console.log(`Posts (24h): ${(d.num_posts || 0).toLocaleString()}`);
console.log(`Engagements: ${(d.interactions || 0).toLocaleString()}`);
console.log(`Sentiment: ${d.sentiment || "N/A"}% bullish`);
console.log(`Contributors: ${(d.num_contributors || 0).toLocaleString()}`);
const posts = await getTopicPosts(team);
console.log(`\nTop Posts:`);
for (const post of (posts.data || []).slice(0, 3)) {
const platform = post.network || "unknown";
const engagement = (post.interactions_total || 0).toLocaleString();
const text = (post.body || post.title || "").slice(0, 100);
console.log(` [${platform}] ${engagement} engagements: ${text}...`);
}
}
teamReport("USA World Cup");
LUNARCRUSH_API_KEY=your_key node index.js
Head-to-head comparison
This is where it gets fun. Pit two rival fan bases against each other:
async function headToHead(teamA, teamB) {
const [a, b] = await Promise.all([
getTopicData(teamA),
getTopicData(teamB),
]);
const dataA = a.data || {};
const dataB = b.data || {};
console.log(`\nHEAD TO HEAD: ${teamA.toUpperCase()} vs ${teamB.toUpperCase()}`);
console.log("=".repeat(55));
const metrics = [
["Posts (24h)", "num_posts"],
["Engagements", "interactions"],
["Sentiment", "sentiment"],
["Contributors", "num_contributors"],
];
for (const [label, key] of metrics) {
const valA = dataA[key] || 0;
const valB = dataB[key] || 0;
const winner = valA > valB ? teamA : valA < valB ? teamB : "TIE";
const format = (v) => key === "sentiment" ? `${v}%` : v.toLocaleString();
console.log(
`${label.padEnd(18)} ${format(valA).padStart(12)} vs ${format(valB).padEnd(12)} --> ${winner}`
);
}
}
headToHead("USA soccer", "Mexico soccer");
Sample output:
HEAD TO HEAD: USA SOCCER vs MEXICO SOCCER
=======================================================
Posts (24h) 48,291 vs 63,817 --> Mexico soccer
Engagements 1,203,441 vs 2,891,003 --> Mexico soccer
Sentiment 72% vs 68% --> USA soccer
Contributors 31,204 vs 44,109 --> Mexico soccer
Run it for any matchup. Argentina vs Brazil. England vs France. Japan vs South Korea. The data is there for all of them.
Rank all 48 teams by buzz
Here's the full leaderboard. Throw every team in, sort by social volume, see who's dominating the conversation:
const TEAMS = [
"Argentina World Cup", "Brazil World Cup", "France World Cup",
"England World Cup", "Germany World Cup", "Spain World Cup",
"USA World Cup", "Mexico World Cup", "Canada World Cup",
"Japan World Cup", "South Korea World Cup", "Australia World Cup",
"Netherlands World Cup", "Portugal World Cup", "Belgium World Cup",
"Croatia World Cup", "Morocco World Cup", "Senegal World Cup",
"Uruguay World Cup", "Colombia World Cup", "Ecuador World Cup",
"Saudi Arabia World Cup", "Qatar World Cup", "Iran World Cup",
];
async function buzzLeaderboard(teams) {
const results = [];
for (const team of teams) {
const data = await getTopicData(team);
const d = data.data || {};
results.push({
team,
posts: d.num_posts || 0,
engagements: d.interactions || 0,
sentiment: d.sentiment || 0,
});
// Small delay to be polite to the API
await new Promise((r) => setTimeout(r, 200));
}
results.sort((a, b) => b.posts - a.posts);
console.log(`\nWORLD CUP 2026 - SOCIAL BUZZ LEADERBOARD`);
console.log("=".repeat(65));
console.log(
`${"#".padEnd(4)}${"Team".padEnd(28)}${"Posts".padStart(10)}${"Engagements".padStart(14)}${"Sentiment".padStart(10)}`
);
console.log("-".repeat(65));
results.forEach((r, i) => {
const name = r.team.replace(" World Cup", "");
console.log(
`${String(i + 1).padEnd(4)}${name.padEnd(28)}${r.posts.toLocaleString().padStart(10)}${r.engagements.toLocaleString().padStart(14)}${(r.sentiment + "%").padStart(10)}`
);
});
}
buzzLeaderboard(TEAMS);
Add or remove teams from the array as the tournament bracket fills out. You can also swap "World Cup" for more specific queries like "USMNT" or "Les Bleus" depending on what fan communities call their team.
Find trending moments
The most interesting thing during a tournament isn't steady buzz -- it's spikes. An upset, a red card, a last-minute goal. This function checks for teams with unusual social activity:
const fs = require("fs");
const STATE_FILE = "./buzz_state.json";
function loadState() {
try { return JSON.parse(fs.readFileSync(STATE_FILE)); }
catch { return {}; }
}
function saveState(state) {
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
}
async function detectSpikes(teams) {
const state = loadState();
const spikes = [];
for (const team of teams) {
const data = await getTopicData(team);
const current = data.data?.num_posts || 0;
const previous = state[team]?.posts || 0;
if (previous > 0 && current > previous * 2) {
spikes.push({
team,
previous,
current,
multiplier: (current / previous).toFixed(1),
});
}
state[team] = { posts: current, timestamp: new Date().toISOString() };
await new Promise((r) => setTimeout(r, 200));
}
saveState(state);
if (spikes.length === 0) {
console.log("\nNo major spikes detected. Check back after a match.");
return;
}
spikes.sort((a, b) => b.multiplier - a.multiplier);
console.log(`\nSPIKE ALERT - Something is happening:`);
console.log("=".repeat(55));
for (const s of spikes) {
const name = s.team.replace(" World Cup", "");
console.log(
`${name}: ${s.previous.toLocaleString()} -> ${s.current.toLocaleString()} posts (${s.multiplier}x increase)`
);
}
}
detectSpikes(TEAMS);
Run this on a cron every 30 minutes during match days:
# crontab -e
*/30 * * * * cd /path/to/world-cup-tracker && LUNARCRUSH_API_KEY=your_key node spikes.js >> spikes.log
When Japan pulls off an upset against Germany (again), you'll see the spike before ESPN runs the highlight reel.
Why social data matters for sports
Traditional sports coverage tells you who scored. Social data tells you:
- Which teams have the most passionate fan bases -- not just the biggest
- Where the conversation is happening -- is it TikTok memes or Reddit analysis?
- Sentiment shifts in real time -- a team can go from beloved to villain in 90 minutes
- Underdog stories forming -- viral moments surface in social data before they hit mainstream media
With 48 teams across three countries, this World Cup is going to generate more social activity than any sporting event in history. Having a way to actually measure and compare that activity is more interesting than refreshing a score app.
Try it
- API key: lunarcrush.com/developers
- Full API docs: lunarcrush.com/developers/api
- MCP server (use with Claude): lunarcrush.com/mcp
Top comments (0)