Tutorial
How to Monitor Twitter/X for Keywords (Automated)
Build an automated pipeline to monitor X/Twitter for keywords. Get notified via cron jobs, webhooks, and Slack or Discord alerts when new tweets match your criteria.
Written by Alex P.
- twitter monitoring
- X alerts
- twitter automation
- keyword monitoring
- twitter webhook
You want to know whenever someone tweets about your product, your competitor, or your niche. Manually checking Twitter isn’t sustainable. Here’s how to automate it.
We’ll build a monitoring pipeline that searches Twitter on a schedule and sends you an alert when new tweets match your criteria. Works for Slack, Discord, email, or any webhook.
The Architecture
Cron (every 5 min) → Search Twitter → Filter by keyword → Deduplicate → Send alert
The complexity is in “search Twitter” — and how you do that determines whether this is reliable or constantly breaking.
Step 1: Get Twitter Data (Three Options)
Option A: X’s Official API (Expensive, Rate-Limited)
// Requires OAuth setup: API key, secret, access token, token secret
const res = await fetch('https://api.twitter.com/2/tweets/search/recent?query=startups', {
headers: { 'Authorization': `Bearer ${bearerToken}` }
});
Why this doesn’t work well for monitoring:
- $100/month minimum for Basic tier (10K posts/mo read limit)
- Aggressive rate limits — 1 request per 15 seconds on some endpoints
- No follower graph on Basic tier
- Complex OAuth setup (4 keys to manage)
- $5,000/month for Pro tier with 1M posts/mo
Option B: DIY Scraping (Fragile)
You can scrape Twitter’s web interface with a headless browser, but X’s anti-bot detection is aggressive. You’ll need proxy rotation ($200-500/month), constant DOM parser maintenance, and you’ll still get blocked periodically.
Option C: FetchLayer API (Recommended)
const res = await fetch('https://api.fetchlayer.dev/twitter/search', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk-your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: 'startup CRM',
product: 'Latest',
count: 25
})
});
const { results } = await res.json();
No OAuth, no rate limit headers, no proxy management. Just JSON. This is what we’ll use for the rest of this guide.
Step 2: Track What You’ve Already Seen
You need to remember which tweets you’ve already processed so you don’t send duplicate alerts. A simple JSON file works:
import { readFileSync, writeFileSync, existsSync } from 'fs';
const SEEN_FILE = './seen-tweets.json';
function loadSeen() {
if (!existsSync(SEEN_FILE)) return new Set();
return new Set(JSON.parse(readFileSync(SEEN_FILE, 'utf-8')));
}
function saveSeen(seen) {
const arr = [...seen].slice(-2000); // Keep last 2000 IDs
writeFileSync(SEEN_FILE, JSON.stringify(arr));
}
Step 3: Filter by Keywords
function matchesKeywords(tweet, keywords) {
const text = tweet.text.toLowerCase();
return keywords.some(kw => text.includes(kw.toLowerCase()));
}
const KEYWORDS = ['crm', 'project management', 'notion alternative'];
const newTweets = results.filter(t => !seen.has(t.id) && matchesKeywords(t, KEYWORDS));
Step 4: Send Alerts
Slack Webhook
async function sendSlackAlert(tweet) {
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `🐦 New tweet from @${tweet.author.handle}`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*<${tweet.url}|@${tweet.author.handle}>*\n${tweet.text.slice(0, 280)}\n${tweet.likeCount} likes · ${tweet.retweetCount} retweets`
}
}
]
})
});
}
Discord Webhook
async function sendDiscordAlert(tweet) {
await fetch(process.env.DISCORD_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
embeds: [{
title: `@${tweet.author.handle}`,
url: tweet.url,
description: tweet.text.slice(0, 280),
color: 0x1da1f2, // Twitter blue
fields: [
{ name: 'Likes', value: String(tweet.likeCount), inline: true },
{ name: 'Retweets', value: String(tweet.retweetCount), inline: true },
{ name: 'Replies', value: String(tweet.replyCount), inline: true },
]
}]
})
});
}
Step 5: Put It All Together
// monitor.mjs
const API_KEY = process.env.FETCHLAYER_API_KEY;
const QUERIES = ['"project management" SaaS', 'startup CRM tool', '"team collaboration" alternative'];
const KEYWORDS = ['crm', 'project management', 'notion alternative'];
async function searchTwitter(query) {
const res = await fetch('https://api.fetchlayer.dev/twitter/search', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, product: 'Latest', count: 25 })
});
const data = await res.json();
return data.results || [];
}
async function run() {
const seen = loadSeen();
let alertCount = 0;
for (const query of QUERIES) {
const tweets = await searchTwitter(query);
const matches = tweets.filter(t => !seen.has(t.id) && matchesKeywords(t, KEYWORDS));
for (const tweet of matches) {
await sendSlackAlert(tweet);
seen.add(tweet.id);
alertCount++;
}
}
saveSeen(seen);
console.log(`[${new Date().toISOString()}] Sent ${alertCount} alerts`);
}
run().catch(console.error);
Step 6: Deploy It
Cron (every 5 minutes)
*/5 * * * * cd /path/to/project && FETCHLAYER_API_KEY=sk-... node monitor.mjs >> monitor.log 2>&1
GitHub Actions
name: Twitter Monitor
on:
schedule:
- cron: '*/5 * * * *'
jobs:
monitor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: node monitor.mjs
env:
FETCHLAYER_API_KEY: ${{ secrets.FETCHLAYER_API_KEY }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Advanced: Monitor by User Instead of Keyword
async function monitorUser(handle) {
const res = await fetch('https://api.fetchlayer.dev/twitter/user-tweets', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ handle, count: 20 })
});
const data = await res.json();
const newTweets = (data.tweets || []).filter(t => !seen.has(t.id));
for (const tweet of newTweets) {
await sendSlackAlert(tweet);
seen.add(tweet.id);
}
}
// Monitor your competitors
const COMPETITORS = ['competitor1', 'competitor2', 'competitor3'];
for (const handle of COMPETITORS) {
await monitorUser(handle);
}
What’s Next
- How to Monitor Replies to a Tweet — track conversations
- How to Track Competitor Tweets — competitor monitoring
- Twitter API with Node.js — full Node.js guide
- FetchLayer API Reference