Back to Blog
Tutorial

Social Media Alerts in Slack: Real-Time Competitor Monitoring Pipeline

April 10, 2026
8 min read
S
By SociaVault Team
SlackMonitoringCompetitor AnalysisIntegrationAlertsAutomation

Social Media Alerts in Slack: Real-Time Competitor Monitoring Pipeline

Your competitors are posting content, gaining followers, and running campaigns — and you hear about it last. Most teams "check" competitor accounts manually, which means sporadically glancing at profiles once a month.

What if every meaningful competitor move landed in a Slack channel automatically?

This guide builds a complete monitoring system: new posts, follower changes, keyword mentions, and viral content alerts — all delivered to Slack in real time.


Architecture Overview

┌─────────────────────────────────────────────┐
│              Cron Scheduler                  │
│         (Every 15 min / hourly)              │
└─────────┬───────────┬──────────┬────────────┘
          │           │          │
     ┌────▼───┐  ┌────▼───┐ ┌───▼────┐
     │ Profile │  │ Posts  │ │ Search │
     │ Monitor │  │ Monitor│ │ Monitor│
     └────┬────┘  └────┬───┘ └───┬────┘
          │           │          │
     ┌────▼───────────▼──────────▼────┐
     │        Change Detector          │
     │   (Compare with stored state)   │
     └─────────────┬──────────────────┘
              ┌────▼────┐
              │  Slack   │
              │ Webhook  │
              └──────────┘

Step 1: Create a Slack Webhook

  1. Go to api.slack.com/apps → Create New App
  2. Choose "From scratch" → name it "Social Monitor"
  3. Go to Incoming Webhooks → Activate → Add to channel (e.g., #competitor-intel)
  4. Copy the webhook URL

Step 2: Follower Change Alerts

The simplest and most useful alert — know when a competitor gains or loses a significant number of followers:

import fs from 'fs';

const SLACK_WEBHOOK = process.env.SLACK_WEBHOOK_URL;
const API_KEY = process.env.SOCIAVAULT_API_KEY;
const STATE_FILE = './monitor-state.json';

const COMPETITORS = [
  { handle: 'competitor1', platform: 'instagram' },
  { handle: 'competitor2', platform: 'tiktok' },
  { handle: 'competitor3', platform: 'twitter' },
];

function loadState() {
  try { return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); }
  catch { return {}; }
}

function saveState(state) {
  fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
}

async function getFollowers(handle, platform) {
  const endpoints = {
    instagram: 'instagram/profile',
    tiktok: 'tiktok/profile',
    twitter: 'twitter/profile',
  };
  
  const res = await fetch(
    `https://api.sociavault.com/v1/scrape/${endpoints[platform]}?handle=${encodeURIComponent(handle)}`,
    { headers: { 'X-API-Key': API_KEY } }
  );
  const data = (await res.json()).data;

  if (platform === 'instagram') return data.follower_count;
  if (platform === 'tiktok') return data.stats?.followerCount;
  if (platform === 'twitter') return data.legacy?.followers_count;
  return 0;
}

async function sendSlack(text, blocks) {
  await fetch(SLACK_WEBHOOK, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ text, blocks }),
  });
}

async function checkFollowerChanges() {
  const state = loadState();

  for (const { handle, platform } of COMPETITORS) {
    const key = `${platform}:${handle}`;
    const current = await getFollowers(handle, platform);
    const previous = state[key]?.followers;

    if (previous && current) {
      const change = current - previous;
      const pct = ((change / previous) * 100).toFixed(2);

      // Alert if change > 1% or > 5,000 followers
      if (Math.abs(change) > previous * 0.01 || Math.abs(change) > 5000) {
        const emoji = change > 0 ? '📈' : '📉';
        const direction = change > 0 ? 'gained' : 'lost';

        await sendSlack(`${emoji} @${handle} ${direction} ${Math.abs(change).toLocaleString()} followers`, [
          {
            type: 'section',
            text: {
              type: 'mrkdwn',
              text: `${emoji} *@${handle}* (${platform}) ${direction} *${Math.abs(change).toLocaleString()}* followers\n` +
                    `• Previous: ${previous.toLocaleString()}\n` +
                    `• Current: ${current.toLocaleString()}\n` +
                    `• Change: ${pct}%`
            }
          }
        ]);
      }
    }

    state[key] = { followers: current, lastChecked: new Date().toISOString() };
    await new Promise(r => setTimeout(r, 1000));
  }

  saveState(state);
}

checkFollowerChanges();

Step 3: New Post Alerts

Get notified within minutes when a competitor publishes new content:

async function checkNewPosts() {
  const state = loadState();

  for (const { handle, platform } of COMPETITORS) {
    const key = `posts:${platform}:${handle}`;
    const knownIds = new Set(state[key]?.postIds || []);

    let posts = [];
    
    if (platform === 'instagram') {
      const res = await fetch(
        `https://api.sociavault.com/v1/scrape/instagram/posts?handle=${encodeURIComponent(handle)}`,
        { headers: { 'X-API-Key': API_KEY } }
      );
      posts = (await res.json()).data || [];
    } else if (platform === 'tiktok') {
      const res = await fetch(
        `https://api.sociavault.com/v1/scrape/tiktok/videos?handle=${encodeURIComponent(handle)}`,
        { headers: { 'X-API-Key': API_KEY } }
      );
      posts = (await res.json()).data || [];
    } else if (platform === 'twitter') {
      const res = await fetch(
        `https://api.sociavault.com/v1/scrape/twitter/user-tweets?handle=${encodeURIComponent(handle)}`,
        { headers: { 'X-API-Key': API_KEY } }
      );
      posts = (await res.json()).data || [];
    }

    const newPosts = posts.filter(p => {
      const id = p.id || p.pk || p.rest_id;
      return id && !knownIds.has(String(id));
    });

    for (const post of newPosts.slice(0, 3)) {
      const caption = post.caption?.text || post.desc || post.full_text || post.legacy?.full_text || '';
      const preview = caption.substring(0, 200);
      
      await sendSlack(`🆕 New post from @${handle}`, [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `🆕 *New ${platform} post from @${handle}*\n\n>${preview}${caption.length > 200 ? '...' : ''}`
          }
        }
      ]);
    }

    // Update known post IDs
    const allIds = posts.map(p => String(p.id || p.pk || p.rest_id)).filter(Boolean);
    state[key] = { postIds: allIds, lastChecked: new Date().toISOString() };

    await new Promise(r => setTimeout(r, 1500));
  }

  saveState(state);
}

Step 4: Keyword Mention Alerts

Monitor when specific keywords appear across platforms — brand names, product names, or competitor mentions:

const KEYWORDS = ['yourbrand', 'your product', 'competitor name'];

async function checkKeywordMentions() {
  const state = loadState();
  
  for (const keyword of KEYWORDS) {
    // Search Twitter/X
    const twitterRes = await fetch(
      `https://api.sociavault.com/v1/scrape/twitter/search?query=${encodeURIComponent(keyword)}`,
      { headers: { 'X-API-Key': API_KEY } }
    );
    const tweets = (await twitterRes.json()).data || [];

    // Search Threads
    const threadsRes = await fetch(
      `https://api.sociavault.com/v1/scrape/threads/search?query=${encodeURIComponent(keyword)}`,
      { headers: { 'X-API-Key': API_KEY } }
    );
    const threads = (await threadsRes.json()).data || [];

    // Search Reddit
    const redditRes = await fetch(
      `https://api.sociavault.com/v1/scrape/reddit/search?query=${encodeURIComponent(keyword)}`,
      { headers: { 'X-API-Key': API_KEY } }
    );
    const redditPosts = (await redditRes.json()).data || [];

    const knownKey = `mentions:${keyword}`;
    const known = new Set(state[knownKey]?.ids || []);

    const allMentions = [
      ...tweets.map(t => ({ id: t.rest_id, platform: 'Twitter', text: t.legacy?.full_text || t.full_text, author: t.core?.screen_name })),
      ...threads.map(t => ({ id: t.id, platform: 'Threads', text: t.caption?.text, author: t.user?.username })),
      ...redditPosts.map(r => ({ id: r.id, platform: 'Reddit', text: r.title, author: r.author }))
    ].filter(m => m.id && !known.has(String(m.id)));

    if (allMentions.length > 0) {
      const summary = allMentions.slice(0, 5).map(m => 
        `• *${m.platform}* — @${m.author}: "${(m.text || '').substring(0, 100)}"`
      ).join('\n');

      await sendSlack(`🔔 ${allMentions.length} new mentions of "${keyword}"`, [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `🔔 *${allMentions.length} new mentions of "${keyword}"*\n\n${summary}`
          }
        }
      ]);
    }

    state[knownKey] = {
      ids: [...tweets, ...threads, ...redditPosts].map(p => String(p.id || p.rest_id)).filter(Boolean),
      lastChecked: new Date().toISOString()
    };

    await new Promise(r => setTimeout(r, 1000));
  }

  saveState(state);
}

Step 5: Viral Content Alert

Get alerted when any tracked competitor has a post that performs significantly above their average:

async function checkViralContent() {
  for (const { handle, platform } of COMPETITORS) {
    if (platform !== 'instagram') continue; // Extend for other platforms

    const res = await fetch(
      `https://api.sociavault.com/v1/scrape/instagram/posts?handle=${encodeURIComponent(handle)}`,
      { headers: { 'X-API-Key': API_KEY } }
    );
    const posts = (await res.json()).data || [];
    if (posts.length < 5) continue;

    // Calculate average engagement
    const engagements = posts.map(p => (p.like_count || 0) + (p.comment_count || 0));
    const avg = engagements.reduce((a, b) => a + b, 0) / engagements.length;

    // Find posts performing 3x above average
    const viral = posts.filter(p => {
      const eng = (p.like_count || 0) + (p.comment_count || 0);
      return eng > avg * 3;
    });

    for (const post of viral.slice(0, 2)) {
      const eng = (post.like_count || 0) + (post.comment_count || 0);
      const multiplier = (eng / avg).toFixed(1);

      await sendSlack(`🔥 Viral post from @${handle}`, [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `🔥 *Viral post detected — @${handle}*\n\n` +
                  `• Engagement: *${eng.toLocaleString()}* (${multiplier}x their average)\n` +
                  `• Likes: ${(post.like_count || 0).toLocaleString()}\n` +
                  `• Comments: ${(post.comment_count || 0).toLocaleString()}\n` +
                  `• Caption: "${(post.caption?.text || '').substring(0, 150)}..."`
          }
        }
      ]);
    }

    await new Promise(r => setTimeout(r, 1000));
  }
}

Step 6: Run Everything Together

Combine all monitors into a single runner:

async function runAllMonitors() {
  console.log(`[${new Date().toISOString()}] Starting monitor cycle...`);
  
  try {
    await checkFollowerChanges();
    console.log('  ✓ Follower check complete');
  } catch (e) {
    console.error('  ✗ Follower check failed:', e.message);
  }

  try {
    await checkNewPosts();
    console.log('  ✓ New post check complete');
  } catch (e) {
    console.error('  ✗ New post check failed:', e.message);
  }

  try {
    await checkKeywordMentions();
    console.log('  ✓ Keyword mention check complete');
  } catch (e) {
    console.error('  ✗ Keyword mention check failed:', e.message);
  }
  
  try {
    await checkViralContent();
    console.log('  ✓ Viral content check complete');
  } catch (e) {
    console.error('  ✗ Viral content check failed:', e.message);
  }

  console.log('Monitor cycle complete.\n');
}

runAllMonitors();

Schedule with cron:

# Run every 30 minutes
*/30 * * * * cd /path/to/monitor && node monitor.js >> monitor.log 2>&1

Slack Channel Organization

Set up dedicated channels for different alert types:

ChannelPurposeAlert Types
#competitor-intelCompetitor movementsFollower changes, new posts
#brand-mentionsBrand monitoringKeyword mentions, sentiment
#viral-contentContent inspirationViral posts from tracked accounts
#social-dailyDaily digestSummary of all changes

Use different webhook URLs for each channel. Separate your monitoring from your daily work channels so alerts don't get buried.


Cost Estimate

MonitorFrequencyCredits/RunMonthly Cost
Follower check (5 accounts)2x/day5300 credits
New posts (5 accounts)4x/day5600 credits
Keyword search (3 keywords, 3 platforms)2x/day9540 credits
Viral detection (5 accounts)1x/day5150 credits
Total~1,590 credits/mo

At SociaVault's pricing, that's roughly $15-20/month for 24/7 competitive intelligence. Compare that to Brandwatch ($800/mo+) or Mention ($300/mo+).


Get Started

Sign up free — set up your first Slack alert in under 10 minutes.


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.