Which World Cup Teams Are Winning the Internet? Tracking Fan Engagement by Country
Halfway through the group stage, a brand strategist is staring at a spreadsheet that says one thing while her timeline says another. On paper, the tournament favorites should be dominating the conversation. But the team that's actually flooding her feed, the one driving the memes, the celebration clips, and the impossibly loud comment sections, is a mid-table side ranked outside the top twenty. Their fans are simply louder, funnier, and more online.
That gap between on-pitch ranking and internet dominance is one of the most interesting stories of any World Cup. The team winning matches and the team winning the internet are often not the same team. And if you're a sponsor deciding where to spend, a media company deciding which storylines to chase, or an analyst trying to explain a brand's spike in mentions, you need to measure it, not guess at it.
This post is about tracking World Cup fan engagement by country using social media data. We'll compare national teams across platforms, normalize the numbers so the comparison is fair, and build a ranking you can rerun after every matchday, with working code in Node.js and Python.
Why "Winning the Internet" Is a Real Metric
It's tempting to dismiss social buzz as vanity. It isn't. Fan engagement by country is a proxy for several things sponsors and media buyers genuinely care about:
- Reach for sponsors. A jersey sponsor or tournament partner gets more value when the team's fans are sharing, celebrating, and tagging. Engaged fanbases turn a logo into earned media.
- Content demand for media. A team with a roaring online fanbase drives clicks, watch time, and subscriptions. Knowing which countries are surging tells an editor where to point the camera.
- Cultural momentum. Sometimes a team's online presence outpaces its results, and that momentum is its own story worth covering.
The point isn't to replace match results with likes. It's to add a second scoreboard, the one measured in attention, that often tells you where the next big moment is brewing.
What We're Measuring, and From Where
For a fair country-by-country comparison we want a handful of signals per national team:
- Follower base on Instagram and TikTok (the size of the standing audience).
- Video plays and likes on recent TikTok content (how active that audience is right now).
- Conversation volume on X and Reddit (how much people are talking, beyond official accounts).
We'll pull all of this through the SociaVault API. Base URL is https://api.sociavault.com, auth is the X-API-Key header, and most endpoints cost one credit per request. If you want to follow along, start free with SociaVault for 50 free credits, and keep the docs handy for the full parameter list.
Here's the shared client.
const API_KEY = process.env.SOCIAVAULT_API_KEY;
const BASE = "https://api.sociavault.com/v1/scrape";
async function sv(path, params = {}) {
const qs = new URLSearchParams(params).toString();
const res = await fetch(`${BASE}${path}?${qs}`, {
headers: { "X-API-Key": API_KEY },
});
if (!res.ok) throw new Error(`SociaVault ${res.status}`);
return res.json();
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
import os, time, requests
API_KEY = os.environ["SOCIAVAULT_API_KEY"]
BASE = "https://api.sociavault.com/v1/scrape"
def sv(path, params=None):
res = requests.get(f"{BASE}{path}", headers={"X-API-Key": API_KEY},
params=params or {}, timeout=30)
res.raise_for_status()
return res.json()
Step 1: Define Your Teams
Start with a simple registry mapping each country to its official handles and a search term for the conversation. You'll build this once and reuse it all tournament.
const TEAMS = [
{
country: "Brazil",
instagram: "cbf_futebol",
tiktok: "cbf_futebol",
query: "Brazil world cup",
},
{
country: "France",
instagram: "equipedefrance",
tiktok: "equipedefrance",
query: "France world cup",
},
{
country: "Argentina",
instagram: "afaseleccion",
tiktok: "afaseleccion",
query: "Argentina world cup",
},
{
country: "Japan",
instagram: "japanfootball",
tiktok: "jfa.official",
query: "Japan world cup",
},
{
country: "Morocco",
instagram: "enmaroc",
tiktok: "enmaroc",
query: "Morocco world cup",
},
// add the rest of your bracket here
];
TEAMS = [
{"country": "Brazil", "instagram": "cbf_futebol", "tiktok": "cbf_futebol", "query": "Brazil world cup"},
{"country": "France", "instagram": "equipedefrance", "tiktok": "equipedefrance", "query": "France world cup"},
{"country": "Argentina", "instagram": "afaseleccion", "tiktok": "afaseleccion", "query": "Argentina world cup"},
{"country": "Japan", "instagram": "japanfootball", "tiktok": "jfa.official", "query": "Japan world cup"},
{"country": "Morocco", "instagram": "enmaroc", "tiktok": "enmaroc", "query": "Morocco world cup"},
]
The handles above are illustrative. Verify the real official accounts for the teams in your bracket before you run this in production, since federation handles change and vary by language.
Step 2: Pull the Numbers Per Team
Now we hit the profile endpoints for follower counts and the search endpoints for conversation volume. We deliberately space out requests so we stay polite and predictable.
async function collectTeam(team) {
const row = { country: team.country };
// Instagram followers
try {
const ig = await sv("/instagram/profile", { handle: team.instagram });
const d = ig.data || ig;
row.igFollowers = d.follower_count || d.followers || 0;
} catch {
row.igFollowers = 0;
}
await sleep(1200);
// TikTok followers + recent video engagement
try {
const tt = await sv("/tiktok/profile", { handle: team.tiktok });
const d = tt.data || tt;
row.ttFollowers = d.stats?.followerCount || d.follower_count || 0;
row.ttLikes = d.stats?.heartCount || d.likes_count || 0;
} catch {
row.ttFollowers = 0;
row.ttLikes = 0;
}
await sleep(1200);
// X conversation volume (proxy: count of matched posts + their engagement)
try {
const x = await sv("/twitter/search", { query: team.query, limit: 50 });
const posts = x.tweets || x.data || [];
row.xPosts = posts.length;
row.xEngagement = posts.reduce(
(s, p) =>
s +
(p.favorite_count || p.likes || 0) +
(p.retweet_count || p.retweets || 0),
0,
);
} catch {
row.xPosts = 0;
row.xEngagement = 0;
}
await sleep(1200);
// Reddit discussion volume
try {
const r = await sv("/reddit/search", { query: team.query, limit: 25 });
const posts = r.posts || r.data || [];
row.redditComments = posts.reduce((s, p) => s + (p.num_comments || 0), 0);
} catch {
row.redditComments = 0;
}
return row;
}
def collect_team(team):
row = {"country": team["country"]}
try:
ig = sv("/instagram/profile", {"handle": team["instagram"]})
d = ig.get("data", ig)
row["ig_followers"] = d.get("follower_count") or d.get("followers") or 0
except Exception:
row["ig_followers"] = 0
time.sleep(1.2)
try:
tt = sv("/tiktok/profile", {"handle": team["tiktok"]})
d = tt.get("data", tt)
stats = d.get("stats") or {}
row["tt_followers"] = stats.get("followerCount") or d.get("follower_count") or 0
row["tt_likes"] = stats.get("heartCount") or d.get("likes_count") or 0
except Exception:
row["tt_followers"], row["tt_likes"] = 0, 0
time.sleep(1.2)
try:
x = sv("/twitter/search", {"query": team["query"], "limit": 50})
posts = x.get("tweets") or x.get("data") or []
row["x_posts"] = len(posts)
row["x_engagement"] = sum(
(p.get("favorite_count") or p.get("likes") or 0) +
(p.get("retweet_count") or p.get("retweets") or 0) for p in posts
)
except Exception:
row["x_posts"], row["x_engagement"] = 0, 0
time.sleep(1.2)
try:
r = sv("/reddit/search", {"query": team["query"], "limit": 25})
posts = r.get("posts") or r.get("data") or []
row["reddit_comments"] = sum(p.get("num_comments") or 0 for p in posts)
except Exception:
row["reddit_comments"] = 0
return row
Step 3: Normalize So the Comparison Is Fair
Here's where most naive comparisons go wrong. Raw totals just rank countries by population and existing fanbase size. Brazil and Argentina have enormous standing audiences, so of course their absolute numbers dwarf a smaller nation's. That tells you nothing new.
To find who's actually winning the internet right now, you want two complementary views:
- Absolute buzz for sponsors who care about total reach.
- Engagement intensity, which normalizes current activity against follower base, to surface punch-above-their-weight fanbases.
def score_teams(rows):
# Min-max normalize each metric to 0..1, then weight and combine.
def normalize(key):
vals = [r.get(key, 0) for r in rows]
lo, hi = min(vals), max(vals)
span = (hi - lo) or 1
return {id(r): (r.get(key, 0) - lo) / span for r in rows}
follow_n = normalize("tt_followers")
xeng_n = normalize("x_engagement")
reddit_n = normalize("reddit_comments")
for r in rows:
# Absolute buzz score
r["buzz_score"] = round(100 * (
0.4 * xeng_n[id(r)] +
0.3 * reddit_n[id(r)] +
0.3 * follow_n[id(r)]
), 1)
# Intensity: engagement relative to audience size (the "loud for their size" metric)
base = r.get("tt_followers") or 1
r["intensity"] = round((r.get("x_engagement", 0) + r.get("reddit_comments", 0)) / base * 1000, 2)
return sorted(rows, key=lambda r: r["buzz_score"], reverse=True)
Run both scoreboards and the story gets interesting. The buzz score tends to reward the big traditional powers. The intensity score is where the surprises live, the smaller nation whose fans are out-posting countries ten times their size. That's the team genuinely winning the internet, and it's exactly the kind of insight a sponsor or editor pays attention to.
Step 4: Track the Change, Not Just the Snapshot
A single snapshot ranks teams. Snapshots over time tell you who's surging. The most valuable signal during a tournament isn't "who is biggest" but "whose engagement just jumped after that last-minute winner."
Save each run with a timestamp and diff against the previous one.
const fs = require("fs");
function saveSnapshot(rows) {
const stamp = new Date().toISOString();
const record = { stamp, rows };
let history = [];
if (fs.existsSync("engagement_history.json")) {
history = JSON.parse(fs.readFileSync("engagement_history.json", "utf8"));
}
history.push(record);
fs.writeFileSync("engagement_history.json", JSON.stringify(history, null, 2));
return record;
}
function surgeReport(history) {
if (history.length < 2) return [];
const prev = history[history.length - 2].rows;
const curr = history[history.length - 1].rows;
const prevMap = Object.fromEntries(prev.map((r) => [r.country, r]));
return curr
.map((r) => {
const before = prevMap[r.country]?.xEngagement || 0;
const delta = r.xEngagement - before;
const pct = before ? Math.round((delta / before) * 100) : 0;
return { country: r.country, delta, pct };
})
.sort((a, b) => b.pct - a.pct);
}
A team jumping 300 percent in conversation between two matchdays is the headline. That's the country whose run is catching fire online, and catching it early is the whole point.
Honest Limits
A few things worth being upfront about:
- You're measuring public signals. Follower counts, public likes, plays, and comment counts are all visible. Private analytics like the federation's own impression or reach data are not, and no public API will give you those.
- Official-account numbers are not the whole fanbase. A federation's Instagram following is one slice. The conversation on X and Reddit captures fans who never follow the official account, which is why we blend multiple signals rather than trusting any single one.
- Language and region skew the conversation view. A team whose fans post mostly in a non-English language may look quieter in an English-keyword search than they really are. Localize your
queryterms per country for a fairer read, and vary TikTokregionto catch local clips. - Official platform APIs are an alternative. Platforms offer their own sanctioned APIs with formal data agreements. They come with approval processes and per-platform integration work. A unified API is the faster path when you want consistent country comparisons across many platforms at once.
Where to Take This Next
Once you've got country rankings, the natural next moves are watching how individual stars drive their nation's numbers, covered in our piece on the World Cup player follower surge, and tracking the live conversation itself in our guide to tracking World Cup buzz in real time. If you're a sponsor, pair this with measuring World Cup sponsorship ROI from social data to connect engagement back to value.
The Takeaway
The team lifting the trophy and the team winning the internet are two different competitions, and only one of them shows up in the league table. With a registry of national-team handles, a few profile and search endpoints, and a normalization step that separates raw size from real intensity, you can run a second scoreboard after every matchday. That scoreboard is where sponsors find value, editors find stories, and analysts find the surprises.
Build your team registry, run your first snapshot before the group stage ends, and let the deltas tell you who's catching fire.
Start free with SociaVault to grab 50 credits and run your first country comparison today.
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.