Updated July 2026: added the 100 free requests starting option and refreshed the official X API pay-per-use cost comparison.
Key takeaway
Real-time Twitter monitoring works by polling a REST endpoint every 5 to 30 seconds, comparing returned tweet IDs against the last seen ID, and pushing new tweets to an alert pipeline. To track many accounts at once, batch up to 5,000 into one X List and poll a single list endpoint.
Why real-time Twitter monitoring got harder after 2023
Real-time Twitter monitoring used to mean one thing: open a persistent connection to the filtered stream, define a few rules, and let tweets flow into your application as they were posted. The 2023 pricing overhaul turned that into a $5,000-a-month Pro-tier feature, and in early 2026 X went further: it switched to a pay-per-use model where every delivered post is billed and a continuous monitoring workload runs into thousands of dollars a month before it hits a 2-million-read cap. For most teams (indie developers, brand-monitoring squads, trading bots, news desks, lead-gen agencies), that pricing killed the project. The practical alternative is to pull the data yourself from a plain REST API.
This guide builds that pull-based pattern on top of Sorsa API, an alternative Twitter/X API provider that returns fresh tweet data on every request, runs on a single API key with no OAuth flow and no approval queue, allows a flat 20 requests per second on every plan, and lets you fold up to 5,000 accounts into one /list-tweets call. Read access starts from $0.02 per 1,000 tweets (and from $0.01 per 1,000 profiles) on batch endpoints, and you can test the whole thing on 100 free requests, one-time and no card required, that cover all 40 endpoints. With response times around 300ms and the right endpoint choice, you can detect a new tweet within seconds of it being posted, and for read-heavy monitoring it costs a fraction of what the official API charges. For the wider comparison of providers, see our Twitter API alternative overview.
A note on terminology: this article uses "polling" deliberately. It is not a euphemism for "delayed." Done right, a polling loop returns a tweet within the interval you choose plus the API response time. If your loop runs every 5 seconds against a 300ms endpoint, your worst-case latency is roughly 5.3 seconds. That matches or beats most consumer-tier streaming products, and crucially it works on a flat-rate REST budget.
Polling vs streaming: which approach fits your case?
There are two ways to get data from a social platform: push-based (streaming, webhooks) or pull-based (polling). Most engineers default to streaming because it sounds faster. In practice, the choice depends on three things: how many things you are monitoring, your latency budget, and your appetite for connection state.
| Factor | Polling (REST) | Streaming (WebSocket / filtered stream) |
|---|---|---|
| Latency to first tweet | Interval + API response time | Connection latency, usually 1 to 3 seconds |
| Setup complexity | One API call in a loop | Persistent connection, reconnect logic, backpressure handling |
| Authentication | API key in a header | OAuth or token rotation |
| Recovery on crash | Resume from saved cursor | Reconnect, replay buffer, dedup window |
| Cost model | Per request | Per delivered resource, or an enterprise contract |
| Best for | 1 to 5,000 monitored targets, alerts with 1 to 30s tolerance | Sub-second latency, full-firehose ingestion, trading at millisecond scale |
Streaming wins when you need millisecond-level alerts on a large keyword space and you have engineering capacity to handle reconnects, gap recovery, and rule management. For everything else (brand listening, news monitoring, lead generation, compliance alerts, mid-frequency trading signals, content moderation), polling is simpler, cheaper, and good enough.
The other underrated advantage of polling: if your script crashes, it picks up where it left off on the next cycle. Streaming pipelines need a separate replay buffer to handle outages without data loss. We do not ship a managed webhook product on purpose; the pull pattern keeps the control plane in your hands, and the flat 20-requests-per-second rate limit gives enough headroom for production-scale loops.
Pick the right endpoint for what you're monitoring
The first design decision is choosing the endpoint that matches the shape of your target. Four endpoints cover virtually every real-time monitoring need.
| Monitoring target | Endpoint | Method | Why |
|---|---|---|---|
| A single account | /user-tweets | POST | Returns the latest tweets from one user's timeline |
| Up to 5,000 accounts at once | /list-tweets | GET | One request covers every member of an X List |
| A keyword, hashtag, or search query | /search-tweets | POST | Full operator syntax, supports order: latest for chronological results |
| @mentions of a specific handle | /mentions | POST | Purpose-built for mention tracking with engagement filters |
The endpoint that surprises most teams is /list-tweets. Putting 50 accounts into a single List and polling the list endpoint cuts request volume by roughly 50 times compared to polling each account individually. The same pattern scales to 500 or 5,000 accounts with no change in request volume, and you create the list itself on x.com.
Level 1: Track a single account
The simplest case. You want to know the moment one account posts: a competitor, a CEO, a regulator, an influencer. Useful for low-volume monitoring or for testing your pipeline before scaling up. It uses the /user-tweets endpoint.
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
USERNAME = "elonmusk"
POLL_INTERVAL = 5 # seconds
URL = "https://api.sorsa.io/v3/user-tweets"
HEADERS = {"ApiKey": API_KEY, "Content-Type": "application/json"}
last_seen_id = None
print(f"Monitoring @{USERNAME}...")
while True:
try:
resp = requests.post(URL, headers=HEADERS, json={"username": USERNAME})
resp.raise_for_status()
tweets = resp.json().get("tweets", [])
if tweets:
# Tweet IDs are Snowflake strings. Cast to int for safe comparison
# since lexicographic order can break across ID length boundaries.
top_id = int(tweets[0]["id"])
if last_seen_id is None:
last_seen_id = top_id
print(f"Baseline set: {last_seen_id}")
else:
new_tweets = [t for t in tweets if int(t["id"]) > last_seen_id]
# Print in chronological order (oldest first).
for tweet in reversed(new_tweets):
print(f"[NEW] @{USERNAME}: {tweet['full_text'][:140]}")
if new_tweets:
last_seen_id = top_id
except requests.exceptions.RequestException as e:
print(f"Request error: {e}")
time.sleep(POLL_INTERVAL * 2)
continue
time.sleep(POLL_INTERVAL)
JavaScript
const API_KEY = "YOUR_API_KEY";
const USERNAME = "elonmusk";
const POLL_INTERVAL = 5000;
let lastSeenId = null;
console.log(`Monitoring @${USERNAME}...`);
while (true) {
try {
const resp = await fetch("https://api.sorsa.io/v3/user-tweets", {
method: "POST",
headers: { "ApiKey": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ username: USERNAME }),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const tweets = (await resp.json()).tweets || [];
if (tweets.length > 0) {
// BigInt comparison avoids precision loss on 64-bit Snowflake IDs.
const topId = BigInt(tweets[0].id);
if (lastSeenId === null) {
lastSeenId = topId;
console.log(`Baseline set: ${lastSeenId}`);
} else {
const newTweets = tweets.filter((t) => BigInt(t.id) > lastSeenId);
for (const t of [...newTweets].reverse()) {
console.log(`[NEW] @${USERNAME}: ${t.full_text.slice(0, 140)}`);
}
if (newTweets.length) lastSeenId = topId;
}
}
} catch (err) {
console.error(`Error: ${err.message}`);
await new Promise((r) => setTimeout(r, POLL_INTERVAL * 2));
continue;
}
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
}
Two details in the code above matter. First, tweet IDs are Snowflake values and arrive as strings. Comparing them as strings works inside a single time window but is fragile across ID length boundaries; cast to int in Python or BigInt in JavaScript. Second, the loop establishes a baseline on the first successful call rather than dumping the entire timeline. That avoids a spam burst on startup.
This works, but it scales poorly. Monitoring 50 accounts means 50 separate polling loops and 50 times the API requests. That is where X Lists come in.
Level 2: Track up to 5,000 accounts in one request
X Lists are the most useful and most underused tool for multi-account monitoring. A List is a public group of accounts (up to 5,000), and the /list-tweets endpoint returns the merged latest tweets across all members in a single request. Build a List once, point your poller at it, and you have effectively built your own custom firehose without paying for the official one.
Step 1: create a public X List
- Go to X Lists and create a new list.
- Add the accounts you want to monitor (up to 5,000).
- Set the list to Public. Private lists are not accessible via the API.
- Copy the List ID from the URL. For
https://x.com/i/lists/1234567890the ID is1234567890.
Step 2: poll the list
import requests
import time
API_KEY = "YOUR_API_KEY"
LIST_ID = "YOUR_LIST_ID"
POLL_INTERVAL = 5
URL = f"https://api.sorsa.io/v3/list-tweets?list_id={LIST_ID}"
HEADERS = {"ApiKey": API_KEY, "Accept": "application/json"}
def monitor_list(callback, interval=POLL_INTERVAL):
"""Poll an X List and call `callback` for each new tweet detected."""
last_seen_id = None
print(f"Monitoring List {LIST_ID} (interval: {interval}s)")
while True:
try:
resp = requests.get(URL, headers=HEADERS, timeout=10)
resp.raise_for_status()
tweets = resp.json().get("tweets", [])
if not tweets:
time.sleep(interval)
continue
top_id = int(tweets[0]["id"])
if last_seen_id is None:
last_seen_id = top_id
print(f"Baseline set: {last_seen_id}")
else:
new_tweets = [t for t in tweets if int(t["id"]) > last_seen_id]
if new_tweets:
for tweet in reversed(new_tweets):
callback(tweet)
last_seen_id = top_id
except requests.exceptions.RequestException as e:
print(f"Request error: {e}. Retrying in {interval * 2}s")
time.sleep(interval * 2)
continue
time.sleep(interval)
def on_new_tweet(tweet):
user = tweet["user"]
print(f"[NEW] @{user['username']}: {tweet['full_text'][:120]}")
print(
f" Likes: {tweet.get('likes_count', 0)} | "
f"RTs: {tweet.get('retweet_count', 0)} | "
f"Views: {tweet.get('view_count', 'N/A')}\n"
)
if __name__ == "__main__":
monitor_list(on_new_tweet)
The efficiency gain is dramatic. Monitoring 50 accounts individually at a 10-second interval costs 432,000 requests per day (50 loops at 8,640 requests each). Putting those same 50 accounts into one X List and polling /list-tweets costs 8,640 requests per day. That is a 50-fold reduction with no loss of coverage.
One gotcha: /list-tweets returns up to 20 tweets per page. If your List members tweet so frequently that more than 20 new tweets arrive within one poll interval, you can miss some. Two fixes: drop the interval to 2 to 3 seconds, or paginate via next_cursor until you reach a previously seen ID. For most use cases (brand monitoring, news desks, audience research), 20 tweets per 5 to 10 seconds is more than enough headroom.
Level 3: Track keywords, hashtags, and search queries
Account-based monitoring catches what known sources say. Keyword-based monitoring catches what anyone says about your topic. Use the /search-tweets endpoint with order: "latest" to get chronological results.
import requests
import time
API_KEY = "YOUR_API_KEY"
QUERY = '"your brand" OR @yourbrand lang:en'
POLL_INTERVAL = 10
URL = "https://api.sorsa.io/v3/search-tweets"
HEADERS = {"ApiKey": API_KEY, "Content-Type": "application/json"}
def monitor_keyword(query, callback, interval=10):
last_seen_id = None
print(f"Monitoring: {query} (interval: {interval}s)")
while True:
try:
resp = requests.post(
URL,
headers=HEADERS,
json={"query": query, "order": "latest"},
timeout=10,
)
resp.raise_for_status()
tweets = resp.json().get("tweets", [])
if tweets:
top_id = int(tweets[0]["id"])
if last_seen_id is None:
last_seen_id = top_id
print(f"Baseline set: {last_seen_id}")
else:
new_tweets = [t for t in tweets if int(t["id"]) > last_seen_id]
for tweet in reversed(new_tweets):
callback(tweet)
if new_tweets:
last_seen_id = top_id
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
time.sleep(interval * 2)
continue
time.sleep(interval)
The real power lives in the query string. The API supports the full Twitter Advanced Search operator set (an unofficial reference is on igorbrigadir/twitter-advanced-search). For example, to track high-engagement English-language mentions of your brand and skip retweets:
monitor_keyword('"your brand" min_faves:10 lang:en -filter:retweets', on_new_tweet)
A few operators that earn their keep in real-time monitoring:
min_faves:N,min_retweets:Nto filter for already-trending content.-filter:retweets,-filter:repliesto drop noise.from:user1 OR from:user2to monitor a handful of accounts without a List.(keyword1 OR keyword2) (problem OR issue OR broken)to catch sentiment-loaded mentions.near:"san francisco" within:25mifor geo-bounded monitoring.
Keyword streams in 2026 are noisier than they used to be: automated replies, scam accounts, and AI-generated spam pile onto any popular term. Engagement thresholds are your cheapest filter at the source. A floor like min_faves:5 or min_replies:2 strips most of the throwaway noise before it ever reaches your callback, so you keep posts with at least some traction. If you are specifically watching mentions of a handle rather than open keywords, the Twitter mentions API exposes the richest filter set (min_likes, min_replies, min_retweets, date bounds) for exactly this kind of cleanup.
If your query string starts to feel unwieldy, the search builder playground lets you construct one visually and see the full operator set as you go.
Push new tweets to Slack, Discord, or any HTTP endpoint
The polling loop is the producer. The callback is where you decide what happens to each new tweet. Because the callback is just a function, the same monitor can route to anything that speaks HTTP. Slack first because it is the most common destination, then a few quick variants.
Slack via Incoming Webhook
import requests
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
def send_to_slack(tweet):
user = tweet["user"]
text = (
f"*New tweet from @{user['username']}*\n"
f"{tweet['full_text']}\n"
f"Likes: {tweet.get('likes_count', 0)} | "
f"RTs: {tweet.get('retweet_count', 0)} | "
f"Views: {tweet.get('view_count', 'N/A')}\n"
f"https://x.com/{user['username']}/status/{tweet['id']}"
)
requests.post(SLACK_WEBHOOK_URL, json={"text": text})
# Plug into any monitor:
monitor_list(send_to_slack)
# or: monitor_keyword("bitcoin lang:en min_faves:50", send_to_slack)
Set up the Slack Incoming Webhook URL in your Slack app settings (official Slack docs). Same pattern for Discord, Telegram, or any internal endpoint.
Discord
DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/YOUR/WEBHOOK"
def send_to_discord(tweet):
user = tweet["user"]
content = (
f"**@{user['username']}** just tweeted:\n"
f"{tweet['full_text']}\n"
f"https://x.com/{user['username']}/status/{tweet['id']}"
)
requests.post(DISCORD_WEBHOOK_URL, json={"content": content})
Discord webhook setup is documented at discord.com/developers/docs/resources/webhook.
Telegram
TELEGRAM_BOT_TOKEN = "YOUR_BOT_TOKEN"
TELEGRAM_CHAT_ID = "YOUR_CHAT_ID"
def send_to_telegram(tweet):
user = tweet["user"]
text = (
f"New tweet from @{user['username']}\n\n"
f"{tweet['full_text']}\n\n"
f"https://x.com/{user['username']}/status/{tweet['id']}"
)
requests.post(
f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage",
json={"chat_id": TELEGRAM_CHAT_ID, "text": text},
)
Any custom HTTP endpoint
def send_to_internal_api(tweet):
requests.post(
"https://internal.example.com/events/twitter",
json={
"tweet_id": tweet["id"],
"username": tweet["user"]["username"],
"text": tweet["full_text"],
"metrics": {
"likes": tweet.get("likes_count", 0),
"retweets": tweet.get("retweet_count", 0),
"views": tweet.get("view_count", 0),
},
"url": f"https://x.com/{tweet['user']['username']}/status/{tweet['id']}",
},
headers={"Authorization": "Bearer YOUR_INTERNAL_TOKEN"},
timeout=5,
)
What you have built is, in effect, your own webhook relay. The API provides the data; your callback decides who hears about each new tweet. The advantage of owning that relay yourself is that you keep all routing rules in your code (filtering, rate limiting, fan-out to multiple channels, retry policy) rather than living inside a vendor dashboard.
How often should you poll, and what does it cost?
Every cycle of your loop costs one request, and one Sorsa request is one unit from your plan no matter which endpoint it hits. The interval you choose drives your monthly usage directly, and on flat per-request pricing it maps cleanly to a plan.
| Interval | Requests / hour | Requests / day | Requests / 30 days | Plan for one loop |
|---|---|---|---|---|
| 1 second | 3,600 | 86,400 | 2,592,000 | Custom (above Enterprise) |
| 5 seconds | 720 | 17,280 | 518,400 | Custom (just above Enterprise) |
| 10 seconds | 360 | 8,640 | 259,200 | Enterprise ($899/mo) |
| 30 seconds | 120 | 2,880 | 86,400 | Pro ($199/mo) |
| 1 minute | 60 | 1,440 | 43,200 | Pro ($199/mo) |
Figures are for a single continuous loop; running several loops in parallel adds their request counts. Plan quotas are Starter 10K, Pro 100K, and Enterprise 500K requests per month, with custom quotas above that.
A few practical guidelines from running these loops in production:
- Brand listening, news monitoring, lead gen: 10 to 30 seconds is plenty. You catch any new tweet within half a minute of posting, and the monthly footprint is small.
- Financial signal detection, breaking news bots, trading workflows: 1 to 5 seconds. You will burn through more requests but the latency budget justifies it.
- Compliance, audit, slow-moving research: 1 to 5 minutes. Real-time is overkill for use cases where the action window is measured in hours.
A 30-second to 1-minute cadence on a single List sits inside the Pro plan at $199 a month. Tightening to a 10-second loop (about 259,000 requests) moves you to Enterprise at $899, and even a 1-second loop on one high-priority target stays far below what the official X API bills for comparable real-time access.
Production hardening: five things to fix before going live
The examples above are deliberately minimal. Before pointing one of these at production traffic, address these five concerns.
1. Persist last_seen_id across restarts
If your script crashes and restarts without remembering its checkpoint, two things go wrong: it either reprocesses old tweets (duplicate alerts to your Slack channel) or sets a fresh baseline and silently misses the gap. Store the checkpoint to a file, Redis, or your database.
import json
import os
STATE_FILE = "monitor_state.json"
def load_state():
if os.path.exists(STATE_FILE):
with open(STATE_FILE) as f:
return json.load(f).get("last_seen_id")
return None
def save_state(last_seen_id):
with open(STATE_FILE, "w") as f:
json.dump({"last_seen_id": last_seen_id}, f)
Load on startup, save after every successful poll that updates the cursor.
2. Exponential backoff on errors
Network issues, transient 5xx responses, and rate-limit hits (HTTP 429) happen. Instead of retrying immediately and making things worse, back off gradually with a cap.
retry_delay = POLL_INTERVAL
MAX_DELAY = 60
while True:
try:
resp = requests.get(URL, headers=HEADERS, timeout=10)
if resp.status_code == 429:
print(f"Rate limited. Backing off {retry_delay}s")
time.sleep(retry_delay)
retry_delay = min(retry_delay * 2, MAX_DELAY)
continue
resp.raise_for_status()
retry_delay = POLL_INTERVAL # reset on success
# process tweets
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
time.sleep(retry_delay)
retry_delay = min(retry_delay * 2, MAX_DELAY)
continue
time.sleep(POLL_INTERVAL)
Back off gradually, cap the delay, and reset to your normal interval on the first success so a brief 429 does not lock your monitor into a slow cadence.
3. Decouple polling from processing
Do not run expensive operations (sentiment scoring, database writes, external API calls, AI classification) synchronously inside the polling loop. If a downstream system slows down, your loop falls behind schedule and latency spikes. Push new tweets into a queue and process them in a separate worker.
from collections import deque
import threading
tweet_queue = deque()
def polling_loop():
"""Fast loop: poll and enqueue. No heavy work here."""
# standard polling code, but instead of calling the callback directly:
# tweet_queue.append(tweet)
pass
def processing_worker():
"""Separate thread: dequeue and dispatch."""
while True:
if tweet_queue:
tweet = tweet_queue.popleft()
send_to_slack(tweet)
save_to_database(tweet)
else:
time.sleep(0.1)
threading.Thread(target=processing_worker, daemon=True).start()
polling_loop()
For heavier workloads, swap the in-memory deque for Redis, RabbitMQ, SQS, or whatever message broker your stack already runs.
4. Health checks and monitoring the monitor
Log every poll cycle: timestamp, new tweet count, response time, errors. Alert if the monitor has not completed a successful poll in the last N minutes. Silent failures are the most expensive kind, especially in alerting pipelines where the absence of alerts means "nothing happened" until someone notices the data gap. You can check the API's operational status at the Sorsa status page to rule out platform issues before debugging your own code.
5. Handle edge cases that bite in production
- Deleted tweets: if a tweet is deleted between when you fetched it and when your callback fires, the URL will 404. Treat this as expected, not an error.
- Protected accounts: if a tracked user goes private,
/user-tweetswill return an empty list. Log and continue. - Pinned tweets: the first tweet in a
/user-tweetsresponse is often the pinned tweet, not the most recent. Sort bycreated_atif you care about strict chronological order. - Retweets vs original tweets:
tweet["retweeted_status"]is populated for retweets. Decide whether you want both or only originals. - Rate-limited replies:
is_replies_limitedindicates the author restricted replies. Useful signal for some monitoring use cases.
In practice: a brand-monitoring migration
A SaaS company we worked with had been running brand monitoring on the official filtered stream since 2018. Their setup tracked roughly 200 keyword rules and 60 priority accounts, and by early 2024 it cost $5,000 a month on the Pro tier. The internal pitch was blunt: cut the stream, save the money, and hope nothing breaks.
The migration took two weeks. We merged the keyword rules into two compound /search-tweets workers (the rules collapsed into boolean queries with OR operators) polling every 30 seconds, and replaced the 60-account follow with one X List polled every 15 seconds. The combined footprint came to about 350,000 requests a month, comfortably inside the Enterprise plan at $899 a month against the $5,000 they had been paying. End-to-end alert latency went from roughly 2 seconds on the filtered stream to roughly 15 seconds at the 90th percentile. For a PR team responding to brand mentions in Slack, the latency change was invisible. The bill change was not.
Use cases where this approach earns its keep
Five patterns we see most often.
Brand listening and social CRM. Poll /search-tweets for your brand handle plus product-name keywords, every 15 to 30 seconds. Route to Slack with sentiment hints baked into the message, and the PR team responds within minutes. This is the core of any brand and social listening setup.
News and signal detection. Build a List of breaking-news handles (Reuters, AP, Bloomberg, regional outlets, beat reporters) and poll it every 5 seconds. Fan out to a Discord server or trading dashboard. This is the cheapest version of a "news firehose" you can build in 2026.
Competitive intelligence. A List of competitor accounts plus their CEOs and product leads, polled every 30 seconds. New tweets land in a shared channel, and your PMM team gets a free intel feed without anyone tabbing through 40 profiles. This is the live layer of an ongoing competitor tracking setup.
Lead generation. Poll /search-tweets for problem-statement queries: "any recommendations for" (CRM OR analytics OR transcription), "looking for an alternative to", "we just churned from". Route to a Slack channel reviewed by sales. Most teams running this catch 5 to 15 qualified leads per week per query bucket. It is the real-time engine behind Twitter lead generation at scale.
Crypto KOL signals. Build a List of crypto influencers and project accounts, poll at 2 to 5 seconds, and optionally weight signals by audience quality using the Sorsa Score endpoints.
Cost vs the official X API for real-time monitoring
Disclosure: Sorsa is our product, so treat this as our read and test any option against your own workload. The numbers on both sides are real and current as of July 2026.
The two providers bill on completely different units. The official X API charges per resource fetched: under the pay-per-use model in effect since early 2026, each post read costs $0.005 and the author profile attached to a tweet is a separate $0.010 user read. Sorsa charges per request, and one request returns roughly 20 tweets (or up to 200 profiles on follower endpoints) with the author data included. That structural gap is what drives the cost difference for monitoring.
| Official X API (pay-per-use, 2026) | Sorsa | |
|---|---|---|
| Pricing model | Per resource fetched | Flat per request (1 call = 1 request) |
| Post reads | $0.005 per post read | Included in the request, no per-post charge |
| Author profile in a tweet | Billed separately, $0.010 per user read | Included free in the tweet response |
| 24/7 monitoring (~1.7M post reads/month) | ~$8,600/month | Enterprise plan, $899/month |
| Monthly read cap | 2M post reads, then Enterprise required | Plan-based quota, no per-post cap |
| Above the cap | Enterprise contract, historically ~$42,000+/month | Custom plan with a raised quota |
| Authentication | OAuth 2.0 + Bearer token | Single API key in a header |
| Rate limit | Varies by endpoint | Flat 20 requests/second on every plan |
The trade-off is latency: the filtered stream lands tweets within a second or two, while polling lands them within your interval plus about 300ms of response time. For most monitoring use cases that gap is invisible. For sub-second trading bots, it is not, and a true stream is the right tool regardless of vendor. But for read-heavy monitoring at brand, news, or competitive scale, paying per delivered post adds up fast and runs into the 2-million-read wall, where a flat per-request plan does not. For the full pricing teardown by use case, see Twitter API pricing in 2026.
FAQ
Is REST polling actually "real-time"?
REST polling is near real-time. The latency budget is the polling interval plus the API response time, which is around 300ms on a fast endpoint. At a 5-second interval, the worst case is roughly 5.3 seconds from a tweet being posted to your callback firing. For the vast majority of monitoring use cases that meets the working definition of real-time; only millisecond-scale trading and live-event auctioning need a true stream.
How many accounts can I monitor with one API key?
With Sorsa, one API key can monitor a practically unlimited number of accounts through X Lists. A single List holds up to 5,000 accounts and counts as one /list-tweets request per poll. Multiple Lists run in parallel within the flat 20 requests-per-second limit, which leaves room for hundreds of concurrent monitoring jobs on a single key.
What happens if I hit the rate limit?
Sorsa returns an HTTP 429 response when you exceed its flat limit of 20 requests per second. Back off for a second, retry, and the loop continues; there is no penalty box or lockout. Most polling cadences stay well under 20 requests per second, so production monitors rarely see a 429 at all, and higher limits are available on request.
How do I avoid duplicate alerts when my script restarts?
To avoid duplicate alerts, persist the last seen tweet ID to durable storage (a file, Redis, or a database) after each successful poll. On startup, load that ID and use it as the baseline so the loop only dispatches tweets newer than the checkpoint. Without persistence, a restart either replays old tweets or silently skips the gap.
Can I monitor private or protected accounts?
No tool can access private or protected Twitter/X accounts, and Sorsa surfaces only public data. If a tracked account goes private mid-monitoring, the endpoint returns an empty list and the polling loop continues without error. This is a platform-level privacy rule, not a limitation specific to any one provider.
Are managed webhooks supported?
Sorsa does not ship a managed webhook product; the supported real-time path is the polling pattern in this guide, where your own callback routes each new tweet. The upside is full control over filtering, fan-out, and retry logic in your own code. Teams that specifically want vendor-hosted push delivery would look to the official X API's Account Activity webhooks at enterprise tiers.
How fresh is the returned data?
The data is fresh on every request, with no caching layer between your call and the platform. If a tweet was posted half a second ago, the next poll picks it up. Combined with response times around 300ms, that freshness is what makes polling viable for monitoring rather than only after-the-fact analytics.
Can I combine real-time monitoring with historical backfill?
Yes, and most production pipelines do both. The same endpoints used for monitoring (/user-tweets, /search-tweets, /list-tweets) accept a next_cursor parameter to page backward through history for a one-time backfill, then you switch to the polling loop for new data going forward. See our guide to historical Twitter data for the backfill side.
Getting started
To build your own real-time monitoring pipeline:
- Grab an API key from the Sorsa dashboard. One key works across all endpoints, and every account starts with 100 free requests (one-time, no card required) so you can wire up a loop before picking a plan.
- Test interactively in the API playground: hit
/user-tweetsor/list-tweetswith a known username or list ID and confirm you see live results. - Copy one of the loops in this article (single account, List, or keyword) and replace the API key.
- Add a callback that routes to wherever you want alerts (Slack, Discord, internal API, queue).
- Add the production hardening (state persistence, backoff, decoupled processing) once the basic loop is stable.
Estimate your monthly usage from the interval table, pick a plan, and ship. The quickstart guide walks through the first call end to end. If you need higher rate limits or volume above the standard plans, talk to sales and we will work out a custom quota.
Reviewed by Keksich, founder of Sorsa, marketer and X API researcher.
This guide is written and maintained by the team that builds and operates Sorsa, an alternative Twitter/X API that has served more than 5 billion requests since 2022. Every code sample runs against the live /user-tweets, /list-tweets, and /search-tweets endpoints, and the polling, backoff, and persistence patterns are taken from monitoring loops we run in production. The cost comparison reflects the official X API pay-per-use model in effect after the April 2026 update, with pricing and rate-limit details verified July 2026. More about the team is on our about page.