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
- Go to api.slack.com/apps → Create New App
- Choose "From scratch" → name it "Social Monitor"
- Go to Incoming Webhooks → Activate → Add to channel (e.g.,
#competitor-intel) - 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:
| Channel | Purpose | Alert Types |
|---|---|---|
#competitor-intel | Competitor movements | Follower changes, new posts |
#brand-mentions | Brand monitoring | Keyword mentions, sentiment |
#viral-content | Content inspiration | Viral posts from tracked accounts |
#social-daily | Daily digest | Summary 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
| Monitor | Frequency | Credits/Run | Monthly Cost |
|---|---|---|---|
| Follower check (5 accounts) | 2x/day | 5 | 300 credits |
| New posts (5 accounts) | 4x/day | 5 | 600 credits |
| Keyword search (3 keywords, 3 platforms) | 2x/day | 9 | 540 credits |
| Viral detection (5 accounts) | 1x/day | 5 | 150 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.
Related Reading
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.