# X (Twitter) Real-Time Monitor: Posts, Follows & Profile Changes (`feeddlabs/x-twitter-user-tracker`) Actor

Monitor X posts and account changes via WebSocket or webhook. Solo: 10 accounts for $20 per 30 days. Free demo: @Reuters only, delayed, no history.

- **URL**: https://apify.com/feeddlabs/x-twitter-user-tracker.md
- **Developed by:** [Feedd Labs](https://apify.com/feeddlabs) (community)
- **Categories:** Social media, News
- **Stats:** 1 total users, 1 monthly users, 86.8% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$20.00 / 1,000 monitoring package credits

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## X (Twitter) Real-Time Monitor: Posts, Follows & Profile Changes

Receive collected posts, follows, unfollows, and profile changes from the
public X accounts you choose in your app, bot, or dashboard. Delivery time
varies by event type, account coverage, and destination.

No X API keys, no scraper to run, and no fee per post.

**$20 for 10 accounts for 30 days.** The Free demo monitors @Reuters only, about a minute behind, with no history.

### Why use it

- **Continuous monitoring.** Receive collected events over a WebSocket or
  webhook, without scheduling a separate run for each check.
- **Straight to Discord, Slack, or Telegram.** Posts show up in your
  channel. Zapier, Make, and n8n work the same way.
- **Replay on paid plans.** Resume from a saved cursor to retrieve retained
  events after a disconnect, within the 30-day retention window.
- **Flat price.** $20 covers 10 accounts for 30 days, however much they post.

### Quick start

1. Run the Actor with your accounts. For a paid plan, first set **Maximum
   cost per run** to at least $20.02 for Solo (the package charge is $20;
   the limit needs $0.02 of headroom). On Apify's Free plan,
   use `"users": ["Reuters"]` and leave out `plan`.

```json
{ "action": "track", "users": ["@elonmusk", "@nasa"], "plan": "solo" }
```

2. Choose where events go:
   - **Discord, Slack, Telegram, Zapier, Make, or n8n:** run `webhook`
     (see below).
   - **Your own app:** copy your stream `key` from the run's OUTPUT (it's
     shown only once) and connect to `wss://ws.feeddlabs.com/stream`.

Some accounts take a few minutes to set up. They start on their own.

### Send events to Discord, Slack, Telegram, Zapier, Make, or n8n

Run the Actor once with your webhook URL. Collected posts, follows,
unfollows, and profile changes are then sent to that destination:

```json
{ "action": "webhook", "webhookUrl": "https://discord.com/api/webhooks/...", "webhookFormat": "discord" }
```

- **Discord:** channel settings, **Integrations**, **Webhooks**, **New
  Webhook**, **Copy Webhook URL**. Format `discord`.
- **Slack:** create an incoming webhook for the channel. Format `slack`.
- **Telegram:** create a bot with @BotFather and add it to your channel or
  group. Pick format `telegram` and fill in **Telegram bot token** and
  **Telegram channel or group** instead of the URL.
- **Zapier, Make, n8n, or your own app:** use their webhook trigger URL.
  Format `json`.

Failed webhook deliveries are retried. On paid plans, retained events can
be replayed within 30 days. To stop, run `webhook` with **Webhook URL** left
empty.

### Send new posts to Google Sheets or email

You can also use Apify's own integrations, such as Google Sheets or email:

1. Set **Action** to `fetch` and tick **Only new since my last run**.
2. Save it as a task and schedule it every few minutes.
3. Add the integration you want to the task.

Each run sends only what's new since the previous run. Posts arrive only as
often as the schedule runs. For delivery without scheduled fetch runs, use a webhook instead.

### Later runs

| Action | What it does |
| --- | --- |
| `track` | Add accounts to your plan. Add `"renew": true` for the next 30 days, or a new `plan` to switch. |
| `fetch` | Export your 100 newest posts and 30 days of follows, unfollows, and profile changes (paid plans). Tick **Only new since my last run** to get only what's new. |
| `webhook` | Send every event to a URL. Leave the URL empty to stop. |
| `status` | See your accounts and when your plan ends. |
| `stop` | Pause an account. Run `track` to resume it. |
| `remove` | Remove an account added by mistake and free its spot: `{ "action": "remove", "user": "@wrong" }`. Two per 30 days. |
| `key` | Get a new stream key: `{ "action": "key", "rotateKey": true }`. |

### For developers: the stream

Connect with your key and read: each message is one event. Save each
event's `cursor`. If you disconnect, reconnect with `?after=` and your last
saved cursor. On paid plans, this resumes retained events after that cursor
within the 30-day retention window. Replay covers events we collected; it
is not a guarantee that every source event was observed.

JavaScript (Node.js, `npm install ws`):

```js
import WebSocket from "ws";

let last = null; // keep this somewhere that survives a restart

function connect() {
  const url = "wss://ws.feeddlabs.com/stream" + (last ? `?after=${last}` : "");
  const ws = new WebSocket(url, {
    headers: { authorization: `Bearer ${process.env.FEED_KEY}` },
  });
  ws.on("message", (raw) => {
    const event = JSON.parse(raw);
    if (event.type === "error") return console.error(event.code);
    if (event.type === "ready") return;
    console.log(event.type, event.account.username);
    last = event.cursor;
  });
  ws.on("close", () => setTimeout(connect, 2000));
}
connect();
```

Python (`pip install websockets`):

```python
import asyncio, json, os, websockets

last = None  # keep this somewhere that survives a restart

async def main():
    global last
    while True:
        url = "wss://ws.feeddlabs.com/stream" + (f"?after={last}" if last else "")
        try:
            async with websockets.connect(
                url,
                additional_headers={"Authorization": f"Bearer {os.environ['FEED_KEY']}"},
            ) as ws:
                async for raw in ws:
                    event = json.loads(raw)
                    if event["type"] in ("ready", "error"):
                        continue
                    print(event["type"], event["account"]["username"])
                    last = event["cursor"]
        except websockets.ConnectionClosed:
            pass
        await asyncio.sleep(2)

asyncio.run(main())
```

Reconnecting without `?after=` continues after the last event we sent you.

#### Checking webhook requests

JSON webhook requests carry `x-feedd-signature: sha256=...`, an HMAC-SHA256
of `x-feedd-timestamp + "." + body` with the secret from the `webhook` run's
OUTPUT (shown once).

### What you get

Every event has the same top part:

| Field | Meaning |
| --- | --- |
| `id` | Unique event ID. If you ever see the same `id` twice (after a reconnect), keep one. |
| `cursor` | Position in your stream. Reconnect with `?after=` and the last one you saved to continue right after it. |
| `time` | When we saw it (UTC). |
| `type` | `post`, `follow`, `unfollow`, or `profile` |
| `account` | The tracked account: `id`, `username`, and `name` |

#### A new post

```json
{
  "id": "K9yTVRR90AAQ4-99tF3_3w",
  "cursor": "1978296",
  "time": "2026-10-09T23:39:24.992Z",
  "type": "post",
  "account": { "id": "17469289", "username": "nypost", "name": "New York Post" },
  "post": {
    "id": "2108703910336565304",
    "url": "https://x.com/nypost/status/2108703910336565304",
    "kind": "post",
    "text": "Steph Curry drops truth bomb on Warriors' front office failure https://trib.al/AsPoEod",
    "links": [],
    "images": ["https://pbs.twimg.com/media/HUOfBvVXYAAepm_.jpg"],
    "videos": [],
    "created_at": "2026-10-09T23:39:24.705Z",
    "complete": true,
    "reference": null
  }
}
```

`kind` is `post`, `reply`, `quote`, or `repost`. For replies, quotes, and
reposts, `reference` holds the other post:

```json
"reference": {
  "type": "replied_to",
  "id": "2108703611391979810",
  "username": "holdenhalford",
  "text": "@bot claim holden@mail.grokbot.com for me",
  "images": [],
  "videos": []
}
```

#### A follow or unfollow

```json
{
  "id": "x5ON5dXuUJIzRZozDEjVS2",
  "cursor": "1978239",
  "time": "2026-10-09T23:38:35.079Z",
  "type": "follow",
  "account": { "id": "1101117625738899458", "username": "skyiszen", "name": "Sky" },
  "target": {
    "id": "180107694",
    "username": "kaitlancollins",
    "name": "Kaitlan Collins",
    "bio": "Chief White House Correspondent and Anchor of The Source at 9 p.m., @CNN"
  }
}
```

#### A profile change

```json
{
  "id": "S6txBPC-9ga010La948bnq",
  "cursor": "1978236",
  "time": "2026-10-09T23:38:27.514Z",
  "type": "profile",
  "account": { "id": "17454769", "username": "glennbeck", "name": "Glenn Beck" },
  "changes": [
    { "field": "pinned_post", "before": null, "after": "2108355025483583891" }
  ]
}
```

`field` is one of `username`, `name`, `bio`, `location`, `website`, `avatar`,
`banner`, or `pinned_post`.

### If something goes wrong

The stream sends `{"type": "error", "code": "..."}` and closes:

| Code | What to do |
| --- | --- |
| `NO_ACTIVE_ENTITLEMENT` | Your plan ended. Run `track` with `"renew": true`. |
| `CURSOR_EXPIRED` | You were away over 30 days. Reconnect to `wss://ws.feeddlabs.com/stream?reset=1`. |
| `INVALID_CURSOR` | That cursor isn't from your stream. Reconnect without `?after=`. |
| `STREAM_KEY_DENIED` | Wrong or old key. Run `key` with `"rotateKey": true`. |
| HTTP 429 or 503 | Wait a few seconds and reconnect. |

### Pricing

| Plan | Accounts | Price for 30 days |
| --- | --- | --- |
| Free demo | @Reuters only | $0, about a minute behind, no history |
| Solo | 10 | $20 |
| Team | 50 | $80 |
| Desk | 150 | $200 |
| Fleet | 500 | $500 |

- **Free:** if you have a free Apify account, run `track` with `"users": ["Reuters"]`
  and leave out `plan`. Other accounts require a paid monitoring package.
- **Paid plans** need a paid Apify subscription. The price is the same
  whichever one you have.

### FAQ

**Do I need X (Twitter) API keys?** No. Everything runs on our side.

**What if my server goes down?** Reconnect with the same key and your last
saved `cursor`. Paid plans can replay collected events retained for up to
30 days. The Free plan has no history replay.

**Can I add or swap accounts?** You can add accounts any time until you
reach your plan's number (10 on Solo). An account you stop still counts
until your next 30 days start. Added the wrong one by mistake? `remove`
frees its spot right away, twice per 30 days.

**Can I track private accounts?** No, only public ones.

**Can AI agents use it?** Yes. Add
`https://mcp.apify.com?tools=feeddlabs/x-twitter-user-tracker` to Claude,
ChatGPT, or Cursor and ask, for example, "Which accounts am I tracking, and
when does my plan end?"

**Something not right?** Write it in **Report a problem** and run the Actor.
The run is free and does nothing else. AI agents can report problems too.

### More from Feedd Labs

| Actor | What it does |
| --- | --- |
| [X (Twitter) Real-Time Monitor](https://apify.com/feeddlabs/x-twitter-user-tracker) | Posts and account changes delivered by WebSocket or webhook |
| [X (Twitter) Search Scraper](https://apify.com/feeddlabs/x-twitter-search) | Tweets and replies by keyword, account and date |
| [X (Twitter) Followers Scraper](https://apify.com/feeddlabs/x-twitter-followers-scraper) | Followers, following and profiles of public accounts |
| [X (Twitter) Account Watch](https://apify.com/feeddlabs/x-account-changes) | Who an account follows or unfollows, and its profile changes |
| [X (Twitter) Ads Library](https://apify.com/feeddlabs/x-ads-library) | Collected promoted ads and brand creative |
| [Mainstream News Live & Archive](https://apify.com/feeddlabs/mainstream-news-archive) | Collected news articles from major outlets |
| [US Stock Market Data](https://apify.com/feeddlabs/us-stock-market-data) | 1-minute stock history since 2016, futures and financial statements |
| [Crypto Derivatives Data](https://apify.com/feeddlabs/crypto-derivatives-data) | Open interest, funding and liquidations on 4 exchanges |
| [Crypto OHLCV & Trades](https://apify.com/feeddlabs/crypto-ohlcv-trades-data) | Candles from 1 second to 1 day and individual trades |
| [Polymarket Data](https://apify.com/feeddlabs/polymarket-odds-history) | Available odds history, volume and open interest by event |

# Actor input Schema

## `action` (type: `string`):

Start with track: it buys your plan, adds your accounts, and gives you your stream key in the OUTPUT. fetch exports your 100 newest posts and 30 days of follows and profile changes, or only what's new since your last run. webhook sends every event to a URL, such as a Discord or Slack channel or a Zapier, Make, or n8n hook. The others check status, pause an account, remove one to free its place (2 per 30 days, for typos), or replace a lost key.

## `user` (type: `string`):

One handle or ID, e.g. @nasa.

## `users` (type: `array`):

Up to 500 handles or IDs per run.

## `accountsDataset` (type: `string`):

Paste the dataset ID of a run of our Search Scraper, Followers Scraper, Account Watch, or Ads Library to track every account in it.

## `plan` (type: `string`):

Solo: 10 accounts for $20. Team: 50 for $80. Desk: 150 for $200. Fleet: 500 for $500. Each lasts 30 days. Set Maximum cost per run at least $0.02 above the package price ($20.02 for Solo).

## `renew` (type: `boolean`):

Buy another 30 days of your plan, starting when the current period ends.

## `onlyNew` (type: `boolean`):

With the fetch action: return only what's new since your last run with this ticked. Schedule it every few minutes and connect Apify's Slack, Google Sheets, or email integrations to it.

## `maxPages` (type: `integer`):

With the fetch action: stop the export after this many pages. The run's OUTPUT says whether the export finished.

## `maxBytes` (type: `integer`):

With the fetch action: stop writing after this many bytes. Rows already written stay in the dataset.

## `webhookUrl` (type: `string`):

With the webhook action: where to send every new post, follow, unfollow, and profile change as it happens. Leave empty to stop sending. For Telegram, fill the two Telegram fields instead.

## `webhookFormat` (type: `string`):

Discord, Slack, and Telegram: a readable message per event, each post once. JSON: the event itself, signed, for Zapier, Make, n8n, or your server.

## `telegramBotToken` (type: `string`):

With the Telegram format: the token @BotFather gives you when you create a bot. Add the bot to your channel or group first. Leave empty to stop sending.

## `telegramChat` (type: `string`):

With the Telegram format: @name of a public channel or group, or its numeric ID (like -1001234567890) for a private one.

## `rotateKey` (type: `boolean`):

With the key action: get a new stream key, for example if you lost yours. The old key stops working and its open stream closes.

## `revokeKey` (type: `boolean`):

With the key action: delete the stream key and close its open stream.

## `reportProblem` (type: `string`):

Something wrong or missing? Write it here and run: we get your message, the run does nothing else, and it's free. AI agents can use this too.

## Actor input object example

```json
{
  "action": "status",
  "renew": false,
  "onlyNew": false,
  "maxPages": 1000,
  "maxBytes": 67108864,
  "webhookFormat": "json",
  "rotateKey": false,
  "revokeKey": false
}
```

# Actor output Schema

## `activity` (type: `string`):

Dataset rows. fetch adds one row per event (post, follow, unfollow, or profile change) with id, cursor, time, type, and account. track, status, stop, webhook, and key add one status row; the stream key and webhook secret stay in OUTPUT only.

## `result` (type: `string`):

The OUTPUT record: the action's result (plan, accounts, charge state, stream key and streamUrl, webhook and its secret, and export counts) or an error code.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "action": "status"
};

// Run the Actor and wait for it to finish
const run = await client.actor("feeddlabs/x-twitter-user-tracker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "action": "status" }

# Run the Actor and wait for it to finish
run = client.actor("feeddlabs/x-twitter-user-tracker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "action": "status"
}' |
apify call feeddlabs/x-twitter-user-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,feeddlabs/x-twitter-user-tracker"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/BeXya0l4fJzYgfmHp/builds/FRavmybI3BfrQRTBq/openapi.json
