X has no free webhook that tells you when an account posts. The usual workaround is polling: ask for the account's newest tweets every few seconds and compare. That works for one account. For fifty it gets expensive, and you still miss tweets between polls.

Account monitoring turns this around. You add the accounts once, and every new tweet arrives at your server as a signed POST. This guide sets it up in Python, from the first account to handling downtime.

What you get

  • Any public account, whatever its size. An account with 50 followers is checked as often as one with 5 million.
  • One event per new tweet, with the full tweet in the simple format: text, author, counts, media, and whether it is a reply, quote or retweet.
  • A check interval of at most 60 s, 5 s or 1 s, depending on the plan.
  • Events are kept for 7 days, so you can read what you missed through the pull API.
ClassGuaranteeTypical
StandardWe check at least every 60 s15–60 s
FastWe check at least every 5 s2–5 s
Real-timeWe check at least every 1 s0.5–1 s

The interval is how often we look. X itself takes a moment before a new tweet shows up in its timelines, and that delay comes on top. Every event carries tweetCreatedAt and detectedAt, so you can measure the real latency.

Step 1: Get an API key and a plan

Create an account at /start and copy your key from the dashboard. Then book a plan under Monitors. The monthly fee comes out of your prepaid balance, and calls to /monitors/* are not billed per request.

AccountsStandard (60 s)Fast (5 s)Real-time (1 s)
10$5$9$19
50$15$29$59
250$49$99$199
1,000$149$299$599
5,000$499$999$1,999

The smallest plan watches 10 accounts for $5 a month. Store the key in an environment variable:

export TWITTRAPI_API_KEY="your-key"

Step 2: Add the accounts to watch

Send up to 100 usernames or user IDs per request:

import os
import requests
 
API = "https://api.twittrapi.com"
HEADERS = {"X-API-KEY": os.environ["TWITTRAPI_API_KEY"]}
 
r = requests.post(
    f"{API}/monitors/accounts",
    headers=HEADERS,
    json={"usernames": ["XDevelopers", "NASA", "OpenAI"]},
    timeout=30,
)
r.raise_for_status()
for result in r.json()["data"]:
    print(result["input"], result.get("status") or result.get("error"))

Each account gets its own result: added, already_watching, or an error such as not found or protected. Protected accounts cannot be watched. Delivery starts with the first tweet after monitoring begins; older tweets are not sent. For history, use the user tweets endpoint.

Step 3: Set your webhook

The URL must be public and use https. Private and local addresses are refused, and redirects are not followed.

r = requests.put(
    f"{API}/monitors/webhook",
    headers=HEADERS,
    json={"url": "https://example.com/hooks/twittrapi"},
    timeout=30,
)
r.raise_for_status()
print(r.json()["webhook"]["secret"])  # whsec_...

The first call returns a signing secret that starts with whsec_. Save it as TWITTRAPI_WEBHOOK_SECRET on your server. Changing the URL later keeps the secret.

Step 4: Receive and verify the events

Every new tweet is one POST with a JSON body. The X-TwittrAPI-Signature header holds a timestamp and an HMAC-SHA256 of <timestamp>.<raw body>, keyed with your secret. Check it on the raw bytes before you parse the JSON, and reject old timestamps so a captured request cannot be replayed.

import hashlib
import hmac
import os
import time
 
from flask import Flask, abort, request
 
SECRET = os.environ["TWITTRAPI_WEBHOOK_SECRET"].encode()
app = Flask(__name__)
 
 
def verify(header: str, raw_body: bytes, tolerance: int = 300) -> bool:
    parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
    t, sig = parts.get("t", ""), parts.get("v1", "")
    if not t.isdigit() or not sig or abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(SECRET, t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)
 
 
@app.post("/hooks/twittrapi")
def hook():
    if not verify(request.headers.get("X-TwittrAPI-Signature", ""), request.get_data()):
        abort(401)
    event = request.get_json()
    if event["event"] == "tweet":
        tweet = event["tweet"]
        print(tweet["author"]["username"], tweet["url"], tweet["text"][:80])
    return "", 204

Answer with any 2xx within 5 seconds. If you need to do more than print, put the event on a queue and answer first. The monitoring docs have the same handler in Node.js and Go.

A tweet event looks like this:

{
  "event": "tweet",
  "id": "1849203",
  "monitoredUserId": "2244994945",
  "tweetId": "2019881223666233717",
  "tweetCreatedAt": "2026-02-06T21:09:45Z",
  "detectedAt": "2026-02-06T21:09:46.212Z",
  "tweet": {
    "id": "2019881223666233717",
    "url": "https://x.com/XDevelopers/status/2019881223666233717",
    "text": "Officially launching X API Pay-Per-Use …",
    "author": { "id": "2244994945", "username": "XDevelopers" },
    "replyTo": null,
    "quoted": null,
    "retweetOf": null
  }
}

Replies and retweets are included. If you only want original posts, skip events where replyTo or retweetOf is set.

Step 5: Send a test event

Before you wait for a real tweet, check that your endpoint answers in time:

r = requests.post(f"{API}/monitors/webhook/test", headers=HEADERS, timeout=30)
print(r.json())  # {"ok": true, "statusCode": 204, "latencyMs": 84}

The test event is signed like a real one and has X-TwittrAPI-Event: test.

Step 6: Catch up after downtime

If your server is down, failed deliveries are retried with growing pauses for up to 24 hours. After 20 failures in a row the webhook is switched off and you get an email; monitoring keeps running. Events stay readable for 7 days through the pull API:

since = None  # store the last nextSince somewhere durable
while True:
    params = {"limit": 500}
    if since:
        params["since"] = since
    page = requests.get(f"{API}/monitors/events", headers=HEADERS, params=params, timeout=30).json()
    for event in page["data"]:
        print(event["tweet"]["url"])
    since = page["nextSince"] or since
    if not page["hasMore"]:
        break

Retries can arrive out of order and, rarely, twice. Use the X-TwittrAPI-Delivery header to drop duplicates and tweetCreatedAt to sort. When the endpoint works again, send PUT /monitors/webhook once more to switch the webhook back on.

Webhook or polling?

Polling the timelineAccount monitoring
CostOne billed call per account per pollFlat monthly fee per plan
DelayYour poll interval60 s, 5 s or 1 s
Work on your sideScheduler, state, deduplicationOne endpoint
Missed tweetsPossible between pollsKept 7 days for catch-up

Polling is fine for a few accounts checked every few minutes. Checking one account every 5 seconds is about 518,000 calls a month, which costs more than a 50-account Fast plan at $29. For keywords and hashtags, which monitoring does not cover, poll search with since_id so each call returns only new tweets.

Frequently asked questions

Does X have a webhook for new tweets?

Yes, through its Activity API, billed per event. We compare it with a flat monitoring plan in X API webhooks for new posts. There is no free option on X.

Can I watch accounts with few followers?

Yes. There is no follower minimum. Every public account in your plan is checked at the plan's interval.

Are deleted tweets reported?

No. An event is sent when a tweet is detected. Deletions do not create a separate event.

What happens when my balance runs out?

The plan is paused at renewal. Your watched accounts are kept, but no events are created until you top up and renew.

Sources

TwittrAPI is not affiliated with X Corp. It reads only public data.