Xには、アカウントが投稿したときに知らせてくれる無料のWebhookがありません。よくある代わりの方法はポーリングで、数秒ごとにアカウントの最新ツイートを取得して前回と比べます。1アカウントならこれで足ります。50アカウントになると費用がかさみ、ポーリングの合間に投稿されたツイートを取りこぼすこともあります。

アカウント監視なら逆の流れになります。アカウントを一度登録すれば、新しいツイートが署名付きのPOSTとしてあなたのサーバーに届きます。このガイドでは、最初のアカウントの登録からサーバー停止時の対応まで、Pythonで設定する手順を説明します。

できること

  • 規模を問わず、公開されているあらゆるアカウントを監視できます。フォロワー50人のアカウントも、500万人のアカウントと同じ頻度で確認します。
  • 新しいツイート1件につき1イベント。シンプル形式(英語)のツイート全体が含まれます。本文、投稿者、各種カウント、メディア、リプライ・引用・リツイートかどうかです。
  • 確認間隔はプランに応じて最長60秒、5秒、1秒です。
  • イベントは7日間保存されるため、取りこぼした分は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

この間隔は当社が確認する頻度です。X側でも新しいツイートがタイムラインに表示されるまで少し時間がかかり、その遅れが加わります。各イベントにはtweetCreatedAtとdetectedAtが含まれるので、実際の遅延を測れます。

ステップ1:APIキーとプランを用意する

こちらでアカウントを作成し、ダッシュボードからキーをコピーします。次に、Monitorsでプランを申し込みます。月額料金は前払い残高から差し引かれ、/monitors/*へのコールはリクエストごとには課金されません。

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

最小のプランは10アカウントで月$5です。キーは環境変数に保存しておきます。

export TWITTRAPI_API_KEY="your-key"

ステップ2:監視するアカウントを追加する

1リクエストで最大100件のユーザー名またはユーザーIDを送れます。

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"))

アカウントごとに結果が返ります。added、already_watching、または「見つからない」「鍵アカウント」などのエラーです。鍵アカウントは監視できません。配信されるのは監視開始後の最初のツイートからで、それより前のツイートは送られません。過去のツイートはユーザーのツイート取得エンドポイント(英語)で取得してください。

ステップ3:Webhookを設定する

URLは公開されていて、httpsである必要があります。プライベートアドレスやローカルアドレスは拒否され、リダイレクトはたどりません。

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_...

最初の呼び出しで、whsec_で始まる署名用シークレットが返ります。サーバーにTWITTRAPI_WEBHOOK_SECRETとして保存してください。あとでURLを変更しても、シークレットは変わりません。

ステップ4:イベントを受信して署名を検証する

新しいツイートごとに、JSONボディを持つPOSTが1回届きます。X-TwittrAPI-Signatureヘッダーには、タイムスタンプと、<timestamp>.<raw body>をシークレットで計算したHMAC-SHA256が入っています。JSONをパースする前に生のバイト列で検証し、古いタイムスタンプは拒否してください。こうすれば、盗まれたリクエストを再送されても通りません。

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

5秒以内に2xxのいずれかを返してください。表示するだけでなく重い処理をするなら、イベントをキューに入れて先に応答します。同じハンドラーのNode.js版とGo版は監視のドキュメント(英語)にあります。

ツイートイベントは次のような形です。

{
  "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
  }
}

リプライとリツイートも含まれます。オリジナルの投稿だけが必要なら、replyToかretweetOfが設定されたイベントを読み飛ばしてください。

ステップ5:テストイベントを送る

実際のツイートを待つ前に、エンドポイントが時間内に応答するか確認します。

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

テストイベントは本物と同じように署名され、X-TwittrAPI-Event: testヘッダーが付きます。

ステップ6:停止中に取りこぼしたイベントを取得する

サーバーが止まっている間、失敗した配信は間隔を広げながら最大24時間リトライされます。20回連続で失敗するとWebhookは無効になり、メールでお知らせします。監視自体は続きます。イベントは7日間、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

リトライは順不同で届くことがあり、まれに2回届きます。重複はX-TwittrAPI-Deliveryヘッダーで除き、並べ替えにはtweetCreatedAtを使ってください。エンドポイントが復旧したら、もう一度PUT /monitors/webhookを送るとWebhookが再び有効になります。

Webhookとポーリング、どちらを使うか

タイムラインのポーリングアカウント監視
費用ポーリング1回・1アカウントごとに課金コール1回プランごとの月額定額
遅延ポーリングの間隔60秒、5秒、1秒
自分で用意するものスケジューラー、状態管理、重複除去エンドポイント1つ
取りこぼしポーリングの合間に起こりうる7日間保存、あとから取得可能

数アカウントを数分おきに確認するだけなら、ポーリングで十分です。1アカウントを5秒ごとに確認すると月に約518,000コールになり、50アカウントのFastプラン($29)より高くつきます。監視の対象外であるキーワードやハッシュタグは、since_idを付けて検索(英語)をポーリングすれば、毎回新しいツイートだけが返ります。

よくある質問

Xには新着ツイートを受け取るWebhookがありますか?

あります。Activity APIで、イベントごとに課金されます。定額の監視プランとの比較は新着ポスト向けX API Webhook(英語)をご覧ください。Xには無料の選択肢はありません。

フォロワーの少ないアカウントも監視できますか?

できます。フォロワー数の下限はありません。プランに登録した公開アカウントは、すべてプランの間隔で確認されます。

削除されたツイートは通知されますか?

されません。イベントはツイートを検知したときに送られます。削除で別のイベントが作られることはありません。

残高がなくなるとどうなりますか?

更新時にプランが一時停止します。監視中のアカウントはそのまま残りますが、チャージしてプランを更新するまでイベントは作られません。

出典

TwittrAPIはX Corp.とは提携していません。公開データのみを読み取ります。