DEV Community

Olamide Olaniyan
Olamide Olaniyan

Posted on

Build a Social Listening Dashboard for Brand Monitoring

Your brand is being mentioned right now. On Twitter, Reddit, TikTok.

Are they praising you? Roasting you? Spreading misinformation?

You have no idea.

In this tutorial, we'll build a Social Listening Dashboard that:

  1. Monitors brand mentions across multiple platforms
  2. Analyzes sentiment in real-time
  3. Alerts you to potential PR crises or viral moments

Stop being the last to know what people say about your brand.

Why Brand Monitoring Matters

The numbers:

  • 96% of people who discuss brands online don't follow the brand
  • Negative posts spread 2x faster than positive ones
  • Responding to complaints within 1 hour increases satisfaction by 80%

Real consequences:

  • United Airlines lost $1.4B in stock value after a viral video
  • Wendy's gained 300K followers from snarky Twitter replies
  • Tesla gets free marketing from owner-generated content

If you're not listening, you're losing.

The Stack

  • Node.js: Runtime
  • SociaVault API: Multi-platform search
  • OpenAI API: Sentiment analysis
  • Express: Simple dashboard server

Step 1: Setup

mkdir brand-monitor
cd brand-monitor
npm init -y
npm install axios openai dotenv express
Enter fullscreen mode Exit fullscreen mode

Create .env:

SOCIAVAULT_API_KEY=your_sociavault_key
OPENAI_API_KEY=your_openai_key
PORT=3000
Enter fullscreen mode Exit fullscreen mode

Step 2: Multi-Platform Search

Create monitor.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';
const headers = { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` };

async function searchTwitter(query) {
  console.log(`🐦 Searching Twitter for "${query}"...`);

  try {
    const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/twitter/search`, {
      params: { query, limit: 30 },
      headers
    });

    const tweets = response.data.data || [];

    return tweets.map(tweet => ({
      platform: 'twitter',
      id: tweet.id || tweet.rest_id,
      text: tweet.full_text || tweet.text,
      author: tweet.user?.screen_name || tweet.author?.username,
      authorName: tweet.user?.name || tweet.author?.name,
      authorFollowers: tweet.user?.followers_count || tweet.author?.followers_count || 0,
      likes: tweet.favorite_count || tweet.likes || 0,
      retweets: tweet.retweet_count || 0,
      replies: tweet.reply_count || 0,
      views: tweet.views_count || 0,
      date: new Date(tweet.created_at),
      url: `https://twitter.com/${tweet.user?.screen_name}/status/${tweet.id}`
    }));
  } catch (error) {
    console.error('Twitter search error:', error.message);
    return [];
  }
}

async function searchReddit(query) {
  console.log(`πŸ”΄ Searching Reddit for "${query}"...`);

  try {
    const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/reddit/search`, {
      params: { query, limit: 30 },
      headers
    });

    const posts = response.data.data || [];

    return posts.map(post => ({
      platform: 'reddit',
      id: post.id,
      text: `${post.title}\n\n${post.selftext || post.body || ''}`.trim(),
      title: post.title,
      author: post.author,
      subreddit: post.subreddit,
      upvotes: post.score || post.ups || 0,
      comments: post.num_comments || 0,
      date: new Date(post.created_utc * 1000),
      url: `https://reddit.com${post.permalink}`
    }));
  } catch (error) {
    console.error('Reddit search error:', error.message);
    return [];
  }
}

async function searchTikTok(query) {
  console.log(`πŸ“± Searching TikTok for "${query}"...`);

  try {
    const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/tiktok/search`, {
      params: { query, limit: 20 },
      headers
    });

    const videos = response.data.data || [];

    return videos.map(video => ({
      platform: 'tiktok',
      id: video.id,
      text: video.desc || video.description || '',
      author: video.author?.uniqueId || video.authorMeta?.name,
      authorName: video.author?.nickname || video.authorMeta?.nickname,
      authorFollowers: video.author?.followerCount || video.authorStats?.followerCount || 0,
      views: video.playCount || video.stats?.playCount || 0,
      likes: video.diggCount || video.stats?.diggCount || 0,
      comments: video.commentCount || video.stats?.commentCount || 0,
      shares: video.shareCount || video.stats?.shareCount || 0,
      date: new Date(video.createTime * 1000),
      url: `https://tiktok.com/@${video.author?.uniqueId}/video/${video.id}`
    }));
  } catch (error) {
    console.error('TikTok search error:', error.message);
    return [];
  }
}

async function searchInstagramHashtag(query) {
  console.log(`πŸ“Έ Searching Instagram for "${query}"...`);

  try {
    // Clean query for hashtag search
    const hashtag = query.toLowerCase().replace(/[^a-z0-9]/g, '');

    const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/instagram/hashtag`, {
      params: { hashtag, limit: 20 },
      headers
    });

    const posts = response.data.data?.posts || response.data.data || [];

    return posts.map(post => ({
      platform: 'instagram',
      id: post.id || post.pk,
      text: post.caption || '',
      author: post.user?.username || post.owner?.username,
      authorFollowers: post.user?.follower_count || 0,
      likes: post.like_count || post.likes || 0,
      comments: post.comment_count || post.comments || 0,
      views: post.play_count || post.video_view_count || 0,
      date: new Date(post.taken_at * 1000),
      url: `https://instagram.com/p/${post.code || post.shortcode}`
    }));
  } catch (error) {
    console.error('Instagram search error:', error.message);
    return [];
  }
}

module.exports = {
  searchTwitter,
  searchReddit,
  searchTikTok,
  searchInstagramHashtag
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Sentiment Analysis Engine

Create sentiment.js:

const OpenAI = require('openai');

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

async function analyzeSentiment(mentions) {
  if (mentions.length === 0) return [];

  // Batch analyze for efficiency
  const batches = chunkArray(mentions, 10);
  const results = [];

  for (const batch of batches) {
    const batchResults = await analyzeBatch(batch);
    results.push(...batchResults);
  }

  return results;
}

async function analyzeBatch(mentions) {
  const textsForAnalysis = mentions.map((m, i) => `[${i}] ${m.text.substring(0, 300)}`).join('\n\n');

  const prompt = `Analyze the sentiment of each social media post about a brand. For each post, determine:

1. Sentiment: "positive", "negative", "neutral", or "mixed"
2. Sentiment score: -1 to 1 (negative to positive)
3. Category: "praise", "complaint", "question", "mention", "comparison", "review", "concern"
4. Urgency: "high" (needs response), "medium", "low"
5. Key topics: 1-3 main topics mentioned

Posts to analyze:
${textsForAnalysis}

Return JSON array with objects for each post index containing: sentiment, score, category, urgency, topics`;

  try {
    const response = await openai.chat.completions.create({
      model: 'gpt-4o-mini',
      messages: [{ role: 'user', content: prompt }],
      response_format: { type: 'json_object' }
    });

    const analysis = JSON.parse(response.choices[0].message.content);
    const results = analysis.posts || analysis.results || Object.values(analysis);

    return mentions.map((mention, i) => ({
      ...mention,
      sentiment: results[i]?.sentiment || 'neutral',
      sentimentScore: results[i]?.score || 0,
      category: results[i]?.category || 'mention',
      urgency: results[i]?.urgency || 'low',
      topics: results[i]?.topics || []
    }));
  } catch (error) {
    console.error('Sentiment analysis error:', error.message);
    return mentions.map(m => ({
      ...m,
      sentiment: 'unknown',
      sentimentScore: 0,
      category: 'mention',
      urgency: 'low',
      topics: []
    }));
  }
}

function chunkArray(array, size) {
  const chunks = [];
  for (let i = 0; i < array.length; i += size) {
    chunks.push(array.slice(i, i + size));
  }
  return chunks;
}

function calculateSentimentSummary(analyzedMentions) {
  const total = analyzedMentions.length;
  if (total === 0) return null;

  const sentimentCounts = {
    positive: 0,
    negative: 0,
    neutral: 0,
    mixed: 0
  };

  const categoryCounts = {};
  const topicCounts = {};
  let totalScore = 0;
  let urgentCount = 0;

  analyzedMentions.forEach(m => {
    sentimentCounts[m.sentiment] = (sentimentCounts[m.sentiment] || 0) + 1;
    categoryCounts[m.category] = (categoryCounts[m.category] || 0) + 1;
    totalScore += m.sentimentScore;
    if (m.urgency === 'high') urgentCount++;

    m.topics?.forEach(topic => {
      topicCounts[topic] = (topicCounts[topic] || 0) + 1;
    });
  });

  const avgScore = totalScore / total;

  // Determine overall sentiment health
  const positiveRatio = sentimentCounts.positive / total;
  const negativeRatio = sentimentCounts.negative / total;

  let health;
  if (positiveRatio > 0.6) health = { status: 'Excellent', emoji: 'πŸ’š', color: 'green' };
  else if (positiveRatio > 0.4) health = { status: 'Good', emoji: 'πŸ’™', color: 'blue' };
  else if (negativeRatio > 0.4) health = { status: 'Concerning', emoji: '🟠', color: 'orange' };
  else if (negativeRatio > 0.6) health = { status: 'Critical', emoji: 'πŸ”΄', color: 'red' };
  else health = { status: 'Neutral', emoji: 'βšͺ', color: 'gray' };

  // Top topics
  const topTopics = Object.entries(topicCounts)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 5)
    .map(([topic, count]) => ({ topic, count, percentage: Math.round((count / total) * 100) }));

  return {
    total,
    sentimentCounts,
    categoryCounts,
    avgScore: avgScore.toFixed(2),
    health,
    urgentCount,
    topTopics,
    positiveRatio: Math.round(positiveRatio * 100),
    negativeRatio: Math.round(negativeRatio * 100),
    neutralRatio: Math.round((sentimentCounts.neutral / total) * 100)
  };
}

module.exports = {
  analyzeSentiment,
  calculateSentimentSummary
};
Enter fullscreen mode Exit fullscreen mode

Step 4: Alert System

Create alerts.js:

function checkAlerts(analyzedMentions, thresholds = {}) {
  const alerts = [];

  const defaults = {
    viralThreshold: 10000, // Views/engagement for viral alert
    negativeSpike: 3, // Number of negative mentions in batch
    influencerThreshold: 50000, // Follower count for influencer alert
    urgentLimit: 2 // Number of urgent mentions
  };

  const config = { ...defaults, ...thresholds };

  // Check for viral mentions
  const viralMentions = analyzedMentions.filter(m => {
    const engagement = (m.views || 0) + (m.likes || 0) * 10 + (m.shares || 0) * 20;
    return engagement > config.viralThreshold;
  });

  if (viralMentions.length > 0) {
    alerts.push({
      type: 'viral',
      severity: 'high',
      emoji: 'πŸš€',
      title: 'Viral Mention Detected',
      message: `${viralMentions.length} post(s) getting significant traction`,
      mentions: viralMentions
    });
  }

  // Check for negative sentiment spike
  const negativeMentions = analyzedMentions.filter(m => m.sentiment === 'negative');
  if (negativeMentions.length >= config.negativeSpike) {
    alerts.push({
      type: 'negative_spike',
      severity: 'high',
      emoji: '⚠️',
      title: 'Negative Sentiment Spike',
      message: `${negativeMentions.length} negative mentions detected`,
      mentions: negativeMentions
    });
  }

  // Check for influencer mentions
  const influencerMentions = analyzedMentions.filter(m => 
    m.authorFollowers >= config.influencerThreshold
  );

  if (influencerMentions.length > 0) {
    alerts.push({
      type: 'influencer',
      severity: 'medium',
      emoji: '⭐',
      title: 'Influencer Mention',
      message: `${influencerMentions.length} mention(s) from high-follower accounts`,
      mentions: influencerMentions
    });
  }

  // Check for urgent responses needed
  const urgentMentions = analyzedMentions.filter(m => m.urgency === 'high');
  if (urgentMentions.length >= config.urgentLimit) {
    alerts.push({
      type: 'urgent',
      severity: 'high',
      emoji: 'πŸ””',
      title: 'Urgent Response Needed',
      message: `${urgentMentions.length} mention(s) require immediate attention`,
      mentions: urgentMentions
    });
  }

  // Check for competitor mentions
  const comparisonMentions = analyzedMentions.filter(m => m.category === 'comparison');
  if (comparisonMentions.length > 0) {
    alerts.push({
      type: 'comparison',
      severity: 'low',
      emoji: 'πŸ”„',
      title: 'Competitor Comparison',
      message: `${comparisonMentions.length} mention(s) comparing to competitors`,
      mentions: comparisonMentions
    });
  }

  // Sort by severity
  const severityOrder = { high: 0, medium: 1, low: 2 };
  alerts.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);

  return alerts;
}

module.exports = { checkAlerts };
Enter fullscreen mode Exit fullscreen mode

Step 5: Dashboard Server

Create server.js:

require('dotenv').config();
const express = require('express');
const { searchTwitter, searchReddit, searchTikTok, searchInstagramHashtag } = require('./monitor');
const { analyzeSentiment, calculateSentimentSummary } = require('./sentiment');
const { checkAlerts } = require('./alerts');

const app = express();
app.use(express.json());

// Store monitoring results
let monitoringData = {
  lastUpdate: null,
  brand: null,
  mentions: [],
  summary: null,
  alerts: []
};

// Main monitoring endpoint
app.get('/api/monitor/:brand', async (req, res) => {
  const brand = req.params.brand;
  const platforms = req.query.platforms?.split(',') || ['twitter', 'reddit', 'tiktok'];

  console.log(`\nπŸ” Monitoring brand: ${brand}`);
  console.log(`Platforms: ${platforms.join(', ')}\n`);

  try {
    // Collect mentions from all platforms
    let allMentions = [];

    if (platforms.includes('twitter')) {
      const twitterMentions = await searchTwitter(brand);
      allMentions.push(...twitterMentions);
    }

    if (platforms.includes('reddit')) {
      const redditMentions = await searchReddit(brand);
      allMentions.push(...redditMentions);
    }

    if (platforms.includes('tiktok')) {
      const tiktokMentions = await searchTikTok(brand);
      allMentions.push(...tiktokMentions);
    }

    if (platforms.includes('instagram')) {
      const instagramMentions = await searchInstagramHashtag(brand);
      allMentions.push(...instagramMentions);
    }

    console.log(`πŸ“Š Found ${allMentions.length} total mentions\n`);

    // Analyze sentiment
    console.log('πŸ€– Analyzing sentiment...\n');
    const analyzedMentions = await analyzeSentiment(allMentions);

    // Calculate summary
    const summary = calculateSentimentSummary(analyzedMentions);

    // Check for alerts
    const alerts = checkAlerts(analyzedMentions);

    // Store results
    monitoringData = {
      lastUpdate: new Date().toISOString(),
      brand,
      mentions: analyzedMentions,
      summary,
      alerts
    };

    res.json(monitoringData);
  } catch (error) {
    console.error('Monitoring error:', error);
    res.status(500).json({ error: error.message });
  }
});

// Get latest data
app.get('/api/latest', (req, res) => {
  res.json(monitoringData);
});

// Get alerts only
app.get('/api/alerts', (req, res) => {
  res.json(monitoringData.alerts);
});

// Get mentions by sentiment
app.get('/api/mentions/:sentiment', (req, res) => {
  const sentiment = req.params.sentiment;
  const filtered = monitoringData.mentions.filter(m => m.sentiment === sentiment);
  res.json(filtered);
});

// Dashboard HTML
app.get('/', (req, res) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Brand Monitor Dashboard</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { 
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      background: #0f172a;
      color: #e2e8f0;
      padding: 20px;
    }
    .container { max-width: 1400px; margin: 0 auto; }
    h1 { font-size: 2rem; margin-bottom: 20px; }
    .search-box {
      display: flex;
      gap: 10px;
      margin-bottom: 30px;
    }
    input {
      flex: 1;
      padding: 12px 16px;
      font-size: 16px;
      border: 1px solid #334155;
      border-radius: 8px;
      background: #1e293b;
      color: #fff;
    }
    button {
      padding: 12px 24px;
      font-size: 16px;
      background: #3b82f6;
      color: white;
      border: none;
      border-radius: 8px;
      cursor: pointer;
    }
    button:hover { background: #2563eb; }
    .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
    .card {
      background: #1e293b;
      border-radius: 12px;
      padding: 20px;
    }
    .card-title { font-size: 0.875rem; color: #94a3b8; margin-bottom: 8px; }
    .card-value { font-size: 2rem; font-weight: bold; }
    .card-subtitle { font-size: 0.75rem; color: #64748b; margin-top: 4px; }
    .positive { color: #22c55e; }
    .negative { color: #ef4444; }
    .neutral { color: #94a3b8; }
    .alerts { margin-bottom: 30px; }
    .alert {
      background: #1e293b;
      border-left: 4px solid;
      padding: 15px 20px;
      margin-bottom: 10px;
      border-radius: 0 8px 8px 0;
    }
    .alert.high { border-color: #ef4444; }
    .alert.medium { border-color: #f59e0b; }
    .alert.low { border-color: #3b82f6; }
    .mentions-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; }
    .mention {
      background: #1e293b;
      border-radius: 8px;
      padding: 15px;
    }
    .mention-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
    }
    .platform-badge {
      padding: 4px 8px;
      border-radius: 4px;
      font-size: 0.75rem;
      background: #334155;
    }
    .mention-text { font-size: 0.875rem; line-height: 1.5; margin-bottom: 10px; }
    .mention-meta { font-size: 0.75rem; color: #64748b; }
    .sentiment-badge {
      display: inline-block;
      padding: 2px 8px;
      border-radius: 4px;
      font-size: 0.75rem;
    }
    .sentiment-badge.positive { background: rgba(34, 197, 94, 0.2); color: #22c55e; }
    .sentiment-badge.negative { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
    .sentiment-badge.neutral { background: rgba(148, 163, 184, 0.2); color: #94a3b8; }
    .loading { text-align: center; padding: 40px; color: #64748b; }
  </style>
</head>
<body>
  <div class="container">
    <h1>🎯 Brand Monitor</h1>

    <div class="search-box">
      <input type="text" id="brandInput" placeholder="Enter brand name to monitor..." value="">
      <button onclick="monitor()">Monitor</button>
    </div>

    <div id="dashboard" class="loading">
      Enter a brand name to start monitoring
    </div>
  </div>

  <script>
    async function monitor() {
      const brand = document.getElementById('brandInput').value;
      if (!brand) return;

      document.getElementById('dashboard').innerHTML = '<div class="loading">πŸ” Searching across platforms...</div>';

      try {
        const res = await fetch('/api/monitor/' + encodeURIComponent(brand));
        const data = await res.json();
        renderDashboard(data);
      } catch (err) {
        document.getElementById('dashboard').innerHTML = '<div class="loading">Error: ' + err.message + '</div>';
      }
    }

    function renderDashboard(data) {
      const { summary, alerts, mentions, brand, lastUpdate } = data;
      if (!summary) {
        document.getElementById('dashboard').innerHTML = '<div class="loading">No mentions found</div>';
        return;
      }

      let html = '<div class="grid">';
      html += '<div class="card"><div class="card-title">Total Mentions</div><div class="card-value">' + summary.total + '</div><div class="card-subtitle">across all platforms</div></div>';
      html += '<div class="card"><div class="card-title">Brand Health</div><div class="card-value">' + summary.health.emoji + ' ' + summary.health.status + '</div><div class="card-subtitle">Avg Score: ' + summary.avgScore + '</div></div>';
      html += '<div class="card"><div class="card-title">Positive</div><div class="card-value positive">' + summary.positiveRatio + '%</div><div class="card-subtitle">' + summary.sentimentCounts.positive + ' mentions</div></div>';
      html += '<div class="card"><div class="card-title">Negative</div><div class="card-value negative">' + summary.negativeRatio + '%</div><div class="card-subtitle">' + summary.sentimentCounts.negative + ' mentions</div></div>';
      html += '</div>';

      if (alerts.length > 0) {
        html += '<div class="alerts"><h2 style="margin-bottom:15px">⚠️ Alerts</h2>';
        alerts.forEach(a => {
          html += '<div class="alert ' + a.severity + '"><strong>' + a.emoji + ' ' + a.title + '</strong><br>' + a.message + '</div>';
        });
        html += '</div>';
      }

      html += '<h2 style="margin-bottom:15px">πŸ“ Recent Mentions</h2>';
      html += '<div class="mentions-grid">';
      mentions.slice(0, 20).forEach(m => {
        html += '<div class="mention">';
        html += '<div class="mention-header"><span class="platform-badge">' + m.platform + '</span><span class="sentiment-badge ' + m.sentiment + '">' + m.sentiment + '</span></div>';
        html += '<div class="mention-text">' + (m.text || '').substring(0, 200) + '...</div>';
        html += '<div class="mention-meta">@' + m.author + ' β€’ ' + formatNumber(m.likes || 0) + ' likes β€’ ' + m.category + '</div>';
        html += '</div>';
      });
      html += '</div>';

      document.getElementById('dashboard').innerHTML = html;
    }

    function formatNumber(n) {
      if (n >= 1000000) return (n/1000000).toFixed(1) + 'M';
      if (n >= 1000) return (n/1000).toFixed(1) + 'K';
      return n;
    }
  </script>
</body>
</html>
  `);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`\n🎯 Brand Monitor Dashboard running at http://localhost:${PORT}\n`);
});
Enter fullscreen mode Exit fullscreen mode

Step 6: Run It

node server.js
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000 and enter a brand name.

Or use the API directly:

curl "http://localhost:3000/api/monitor/tesla?platforms=twitter,reddit,tiktok"
Enter fullscreen mode Exit fullscreen mode

Sample Dashboard Output

πŸ” Monitoring brand: tesla
Platforms: twitter, reddit, tiktok

🐦 Searching Twitter for "tesla"...
πŸ”΄ Searching Reddit for "tesla"...
πŸ“± Searching TikTok for "tesla"...
πŸ“Š Found 87 total mentions

πŸ€– Analyzing sentiment...

═══════════════════════════════════════════════════════════
πŸ“Š BRAND MONITORING REPORT: TESLA
═══════════════════════════════════════════════════════════

πŸ“ˆ SUMMARY
─────────────────────────────────────────────────────────
Total Mentions: 87
Brand Health: πŸ’™ Good
Average Sentiment Score: 0.34

Sentiment Breakdown:
  πŸ’š Positive: 38 (44%)
  βšͺ Neutral: 31 (36%)
  πŸ”΄ Negative: 18 (21%)

⚠️ ALERTS (3)
─────────────────────────────────────────────────────────
πŸš€ HIGH: Viral Mention Detected
   2 post(s) getting significant traction

⭐ MEDIUM: Influencer Mention
   3 mention(s) from high-follower accounts

πŸ”” HIGH: Urgent Response Needed
   4 mention(s) require immediate attention

πŸ“ TOP TOPICS
─────────────────────────────────────────────────────────
1. Cybertruck (23%)
2. FSD (18%)
3. Model Y (15%)
4. Stock (12%)
5. Charging (9%)

πŸ”΄ NEGATIVE MENTIONS REQUIRING ATTENTION
─────────────────────────────────────────────────────────

1. [Twitter] @angry_customer
   "3 weeks waiting for Tesla service appointment..."
   Urgency: HIGH | Category: complaint

2. [Reddit] r/teslamotors
   "Phantom braking happened again on my MY..."
   Urgency: HIGH | Category: concern

3. [Twitter] @tech_reviewer
   "Compared Tesla FSD to Waymo today. Not even close..."
   Urgency: MEDIUM | Category: comparison

πŸ’š POSITIVE MENTIONS
─────────────────────────────────────────────────────────

1. [TikTok] @ev_enthusiast (125K followers)
   "Tesla road trip across the country..."
   Engagement: 234K views | Category: praise

2. [Twitter] @happy_owner
   "Just hit 100K miles on my Model 3. Zero issues..."
   Engagement: 1.2K likes | Category: review
Enter fullscreen mode Exit fullscreen mode

What You Just Built

Enterprise social listening tools are expensive:

  • Brandwatch: $800+/month
  • Sprinklr: $1000+/month
  • Mention: $41+/month
  • Brand24: $99+/month

Your version monitors across platforms for cents per search.

Features You Can Add

  1. Scheduled monitoring: Run checks every hour
  2. Email/Slack alerts: Send notifications
  3. Competitor comparison: Track multiple brands
  4. Trend tracking: Monitor sentiment over time
  5. Response suggestions: AI-generated reply templates

Get Started

  1. Get your SociaVault API Key
  2. Run the server
  3. Monitor your brand

Stop being surprised by viral crises. Start listening in real-time.


Your brand has a reputation. Know what it is.

Top comments (0)