Twitter (X) Alerts for Keywords — A Developer's Guide
A keyword-alert system for Twitter (X) is one of the most common dev workflows around the API — track a brand mention, a campaign hashtag, a competitor reference, or a market-moving phrase, and route matching tweets to your team in real time. Dashboard tools sell this as a polished UI; the dev path is direct API + your own delivery integration, which costs a few dollars a month at meaningful volumes.
This guide walks the full production pattern: pick the API path, structure the polling loop, dedupe tweet IDs, score each match against alert criteria, and deliver via email or webhook. Runnable Python end-to-end with per-call cost from each provider's published pricing page. Most top SERP results on this query are surface-level walkthroughs; this one is the code-level recipe.
The five-step pattern — keyword → poll → dedupe → score → deliver
Every Twitter keyword-alert system, no matter the UI on top, decomposes into the same five steps:
1. Define the keyword(s) — One term, a phrase, a boolean expression (OR between alternatives, - for negation), with optional engagement / language / date filters baked into the query.
2. Poll the search API — Hit /twitter/tweet/advanced_search (twitterapi.io) or /2/tweets/search/recent (X official) on a cadence. Every 5 minutes is the typical baseline.
3. Dedupe by tweet ID — Every successive poll may return the same tweet again. Maintain a seen set of IDs; skip any ID you've already processed.
4. Score against alert criteria — Each new tweet is evaluated against your filter (minimum engagement, sentiment threshold, author whitelist, regex on the body). Matches advance to step 5.
5. Deliver — Push the match through your delivery channel: email (SES, SendGrid), webhook (Slack, Discord, custom HTTP), or push notification (your mobile app). Track delivery state so failures don't double-fire.
Path 1 — twitterapi.io advanced_search (recommended for cost)
twitterapi.io's advanced_search accepts the full X advanced-search expression as the query parameter. Auth is X-API-Key header. Pricing per twitterapi.io/pricing: $0.00015 per returned tweet.
Pick this for any meaningful polling workload — the per-call cost compounds favorably as you scale the number of tracked keywords.
import os, requests, json, time
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
def poll_keyword(keyword: str, min_faves: int = 10):
"""Single poll for matches on this keyword."""
r = requests.get(
f"{BASE}/twitter/tweet/advanced_search",
headers=HEADERS,
params={"query": f"{keyword} min_faves:{min_faves}", "queryType": "Latest"},
timeout=15,
)
r.raise_for_status()
return r.json().get("tweets", [])
seen = set()
KEYWORD = "vector database"
while True:
matches = poll_keyword(KEYWORD, min_faves=10)
new = [t for t in matches if t["id"] not in seen]
for t in new:
seen.add(t["id"])
# Deliver via your preferred channel (email / webhook / Slack)
print(f"\u26a0 alert: {t.get('text', '')[:140]}")
time.sleep(300) # 5 min
Path 2 — X official `/2/tweets/search/recent`
X's official search endpoint accepts the same operator set. Auth is a bearer token from the X Developer Console. Pricing per docs.x.com/x-api/getting-started/pricing: $0.005 per post read, with 24h UTC dedup window.
Pick this when you're already on the X bill (you also write or read other surfaces); marginal alert-poll cost rides on the same auth.
# pip install tweepy
import tweepy
client = tweepy.Client(bearer_token="YOUR_X_BEARER")
seen = set()
KEYWORD = "vector database"
import time
while True:
resp = client.search_recent_tweets(
query=f"{KEYWORD} min_faves:10",
max_results=100,
tweet_fields=["created_at", "public_metrics", "author_id"],
)
for t in resp.data or []:
if t.id in seen:
continue
seen.add(t.id)
# Deliver via your channel
print(f"\u26a0 alert: {t.text[:140]}")
time.sleep(300) # 5 min
Scoring — what 'matches your criteria' actually means
Raw matches from the API are a stream of tweets containing the keyword. Most production alert systems add a scoring layer before delivery to keep the signal-to-noise ratio high:
Engagement threshold — min_faves:N in the query handles most cases. For finer control, post-fetch filter on public_metrics.like_count / retweet_count / reply_count against your own threshold.
Author whitelist or blacklist — Restrict to accounts you trust (from:userA OR from:userB) or exclude accounts you don't (-from:botC). Build the operator into the query if the list is small; client-side filter if it's larger.
Sentiment — Pass the tweet text to an LLM (OpenAI, Anthropic) with a sentiment-classifier prompt. Cost depends on the classifier; typical cents per 100 tweets via gpt-4-class models. Skip negatively-scored matches if your use case is opportunity-detection.
Body regex / structured match — If you need to extract a number (price, URL, ticker symbol) from the tweet, regex-match before delivering. Forward only structured-match successes.
Delivery channels — email, webhook, Slack, push
Three common patterns for routing matches to your audience:
Email — Amazon SES is cost-efficient ($0.10 per 1,000 emails per docs.aws.amazon.com/ses/), SendGrid is friendlier for templating. Batch matches into one email per N minutes for non-urgent alerts; one email per match for urgent.
Webhook — POST the tweet record (or your summary) to a Slack incoming webhook, a Discord webhook, or your own HTTP endpoint. Slack's per-channel rate limit is documented at api.slack.com — bursting too fast triggers throttling.
Push notification — Your mobile app's push provider (Apple APNs, Google FCM, OneSignal). Useful for in-app product features rather than bulk alerts.
Pick by your audience's preference — many production systems run all three in parallel for redundancy.
Side-by-side comparison — 3 alert architectures
Same pattern (alert when keyword matches) framed across three architectural choices. Per-call costs derived from each provider's published pricing.
Three patterns: (a) per-call cost compounds — at any non-trivial volume the twitterapi.io path is materially cheaper, derivable from cited rates; (b) dashboard tools win on plug-and-play but lose on custom scoring + delivery flexibility; (c) most production teams run a self-built alert system because the scoring rules are domain-specific and not in any dashboard's UI.
Picking a path — the decision rule
Building keyword alerts as a product feature inside your own app? → twitterapi.io + your own scoring + your own delivery. The cost structure makes per-customer alerts economically viable.
Already on the X bill, alerts are a side-feature? → X official; marginal cost rides on the same auth.
Need plug-and-play UI for a non-technical team? → dashboard tool (Brand24, Mention, etc.). Pay the seat fee, get a polished UI; lose the custom-scoring + custom-delivery flexibility.
Most dev-built systems are a hybrid: twitterapi.io for the polling layer + custom code for scoring + your existing email/webhook infrastructure. Total monthly bill for moderate-volume tracking ($0.00015/tweet × ~10K tracked tweets per month) is under $2 in API credits.
# Production-ready alert system: poll, dedupe, score, deliver.
import os, requests, json, time, hashlib
from datetime import datetime, timezone
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
class KeywordAlertSystem:
def __init__(self, keyword: str, min_faves: int = 10):
self.keyword = keyword
self.min_faves = min_faves
self.seen = set()
# Persist seen IDs to disk so a restart doesn't re-alert
self.state_file = f"alert_state_{hashlib.md5(keyword.encode()).hexdigest()[:8]}.json"
self._load_state()
def _load_state(self):
try:
with open(self.state_file) as f:
self.seen = set(json.load(f))
except FileNotFoundError:
pass
def _save_state(self):
with open(self.state_file, "w") as f:
json.dump(list(self.seen)[-10000:], f) # cap memory
def poll(self):
r = requests.get(
f"{BASE}/twitter/tweet/advanced_search",
headers=HEADERS,
params={"query": f"{self.keyword} min_faves:{self.min_faves}"},
timeout=15,
)
r.raise_for_status()
return r.json().get("tweets", [])
def score(self, tweet) -> bool:
"""Replace with your real scoring rule."""
pm = tweet.get("public_metrics", {})
return pm.get("like_count", 0) >= self.min_faves * 5
def deliver(self, tweet):
"""Replace with your channel (SES, Slack webhook, etc.)."""
ts = datetime.now(timezone.utc).isoformat()
print(f"[{ts}] \u26a0 {self.keyword} alert: {tweet.get('text', '')[:140]}")
def run(self, cadence_sec: int = 300):
while True:
try:
matches = self.poll()
for t in matches:
if t["id"] in self.seen:
continue
self.seen.add(t["id"])
if self.score(t):
self.deliver(t)
self._save_state()
except Exception as e:
print(f"poll error: {e}")
time.sleep(cadence_sec)
if __name__ == "__main__":
KeywordAlertSystem("vector database", min_faves=20).run()
# Cost framing (math from cited pricing pages):
# 5-min poll, 24h/day = 288 calls/day per tracked keyword
# Assume ~5 tweets returned per call = 1,440 tweets/day per keyword
# twitterapi.io: 1,440 × $0.00015 = $0.216/day = ~$6.50/mo per keyword
# X official: 1,440 × $0.005 = $7.20/day = ~$216/mo per keyword
# Track 100 keywords → $650/mo via twitterapi.io vs $21,600/mo via X.Questions readers ask
How often should I poll the search API?
5 minutes is the practical baseline. X tweets surface within seconds, but the search API returns the same recent set if you poll faster — so polling every minute mostly returns duplicates. For urgent events (market-moving keywords), poll every 1-2 min; for routine monitoring, every 5-15 min.
Do I need a separate poller per keyword, or can I batch?
Boolean OR in the query batches keywords: "vector database" OR "vector search" OR "embedding model". Each unique returned tweet is one billable unit. Group by which keyword matched client-side. For tracking many keywords this is materially cheaper than separate polls.
How do I avoid duplicate alerts on restart?
Persist the seen set of tweet IDs to disk (or a small database). The example in the code section saves to a JSON file keyed by keyword hash; in production use Postgres / Redis / SQLite. Cap the size to avoid unbounded growth — IDs older than the search window can be evicted.
What's a reasonable scoring rule for high-signal alerts?
It depends on your domain. Common starters: like_count >= 100 for genuinely-engaged matches, verified=true for high-trust authors, or LLM sentiment-classify with a threshold. For market-moving topics use velocity (tweet appears at engagement rate > X likes/min). Tune against your own captured data — calibrate against past examples where the alert would have been useful.
Can I deliver to Slack and email simultaneously?
Yes. The deliver() step can call multiple downstream channels in sequence. Common pattern: write the match to a single source-of-truth table, then run a separate worker that picks up unprocessed rows and dispatches to each subscribed channel. Decoupling channels from polling reduces blast radius if one delivery system is down.
What happens at scale — 1,000 keywords, 24h/day?
Math from cited pricing. twitterapi.io: 1,000 × $6.50/mo = $6,500/mo (and you'd batch many keywords per poll via OR queries to reduce this). X official: $216,000/mo at same volume — typically infeasible. At this scale your bill is dominated by the polling layer, not delivery; pick the API path on cost-per-call alone.
Continue
- twitterapi.io — pricing
- X API — pricing (docs.x.com, 2026 verified)
- X official — Build a query (operators)
- Amazon SES — email delivery docs
- Twitter (X) API — cluster hub
- Twitter (X) Advanced Search API guide
- Twitter (X) hashtag tracker tools
- Monitoring Twitter via API — architectural guide
- twitterapi.io pricing
Stop reading. Start building.
Starter credits cover real testing on real data. Google sign-in, no card, no application queue.
Get an API key