I wanted a single screen that told me what the internet was feeling during a World Cup match — which teams and players were spiking, whether the mood was joy or rage, and which moments were about to go viral. The enterprise social listening tools that do this cost more per month than my rent. So I built my own over a weekend.
Here's the architecture and the code that matters.
What I wanted on the screen
- Live conversation volume per team/player (who's spiking right now)
- Sentiment per platform (X furious, TikTok celebrating — they're rarely the same)
- A simple "something's happening" alert when volume jumps
- Refresh every couple of minutes during the match
The data layer
I didn't want to wrangle five different official APIs (and get rate-limited or rejected by most of them). I used the SociaVault API so every platform speaks the same shape behind one key.
A single fetch helper covers everything:
const API_KEY = process.env.SOCIAVAULT_API_KEY;
const BASE = "https://api.sociavault.com";
async function sv(path, params) {
const url = new URL(BASE + path);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
const res = await fetch(url, { headers: { "X-API-Key": API_KEY } });
if (!res.ok) throw new Error(`${res.status}`);
return res.json();
}
Pulling the conversation for a topic
For a given match or player, I pull recent posts/comments from a few platforms and normalize them to plain text strings. The sentiment step doesn't care which platform a string came from:
async function gatherConversation(query) {
const [x, tiktok] = await Promise.all([
sv("/v1/scrape/twitter/search", { query, limit: "100" }),
sv("/v1/scrape/tiktok/search/keyword", { query, limit: "100" }),
]);
return {
x: (x.tweets ?? x.data ?? []).map(t => t.text ?? ""),
tiktok: (tiktok.data ?? []).map(v => v.desc ?? ""),
};
}
A transparent sentiment score
I started with a tiny lexicon scorer. It's crude, but it's transparent and fast, and you can tune it for football slang (in match conversation, "penalty" and "VAR" usually skew negative because they show up when fans are furious):
const POSITIVE = new Set(["amazing", "goal", "wins", "class", "magic", "hero"]);
const NEGATIVE = new Set(["var", "penalty", "robbed", "disgrace", "offside", "bottled"]);
function scoreCorpus(texts) {
let pos = 0, neg = 0;
for (const t of texts) {
for (const w of t.toLowerCase().split(/\W+/)) {
if (POSITIVE.has(w)) pos++;
if (NEGATIVE.has(w)) neg++;
}
}
const total = pos + neg || 1;
return { score: (pos - neg) / total, pos, neg, n: texts.length };
}
Run it per platform and the spread is the insight. During a contentious match you'll often see TikTok positive (highlights), X negative (refereeing rage), Reddit in the middle. A single blended number would hide all of that.
The "something's happening" alert
Volume is the earliest signal. Store a rolling baseline, and when the latest window jumps well above it, fire an alert:
function isSpike(current, baseline, factor = 2.5) {
return baseline > 0 && current >= baseline * factor;
}
That's the whole trick behind "catching a moment early" — you're watching the derivative, not the absolute count.
The front end
Honestly, the front end is the easy part. I threw the normalized numbers into a basic React dashboard with a few line charts (volume over time per topic, sentiment over time per platform) and a list of active alerts. Poll the backend every 90 seconds during a match and you've got a live cockpit.
Cost reality check
Each search call is one credit. Polling a handful of topics every 90 minutes... sorry, every 90 seconds, during a 2-hour match adds up, so I cache aggressively and diff results so I never reprocess content I've already seen. Still came out to a few dollars for a full match day.
The full write-up
I documented the complete dashboard build — backend, caching, charts, the whole thing — here: Build a World Cup Social Listening Dashboard. The real-time monitoring foundation is in Tracking World Cup Buzz in Real Time, and the sentiment deep-dive is here.
Free key with 50 credits at sociavault.com if you want to build your own. Genuinely one of the more fun weekend projects I've done — the moment your sentiment line tanks two seconds after a VAR decision, you feel like you've tapped into the planet's mood.
Top comments (0)