On this page

On this page

Mainstream messaging

X / Twitter

The X plugin turns mentions of your bot account into agent conversations and posts the agent's answer as a public reply. Only allowlisted authors can trigger a reply by default. Unknown authors are silently dropped; they receive no pairing prompt or other response.

Each X conversation is a group thread identified by its conversation_id. The agent receives the triggering mention together with available ancestors, conversation posts, and quoted posts. The original mention remains the user-visible message. DMs, original posts, likes, follows, and media uploads are not supported.

Setup

The plugin is bundled with builds that include extensions/x. To add it to an OpenClaw 2026.9.8 installation from a local checkout:

bash
openclaw plugins install --link ./extensions/x

Use an X confidential OAuth2 application and authorize the bot account with tweet.read tweet.write users.read offline.access. Keep its client ID, client secret, and user-context refresh token. The plugin refreshes access tokens with HTTP Basic client authentication. An optional, separate app-only bearer token enables the Activity API.

X can rotate the refresh token when issuing an access token. The plugin saves the latest token in private, worker-backed plugin state (x.oauth) so it survives restarts. Changing the configured refresh-token seed starts a new token lineage. This storage does not promise encryption at rest; protect the Gateway's state directory and its backups as credentials.

Set the bot's numeric user ID and username, then add at least one maintainer's numeric X user ID to allowFrom:

json5
{  channels: {    x: {      enabled: true,      userId: "123456789",      username: "example_bot",      clientId: "example-x-client-id",      clientSecret: { source: "env", provider: "default", id: "X_CLIENT_SECRET" },      refreshToken: { source: "env", provider: "default", id: "X_REFRESH_TOKEN" },      bearerToken: { source: "env", provider: "default", id: "X_BEARER_TOKEN" },      allowFrom: ["987654321"],      groupPolicy: "allowlist",      dmPolicy: "disabled",      events: { mode: "auto", pollSeconds: 60 },      threadContext: { maxPosts: 50 },    },  },  bindings: [{ agentId: "main", match: { channel: "x" } }],}

Replace the example IDs and username. Make the referenced environment variables available to the Gateway. Omit bearerToken to use polling without the Activity API. All three secret fields also accept plaintext or supported SecretRef inputs.

Run openclaw config validate and openclaw channels status. From an allowlisted account, mention the bot in a post. A successful turn produces a public reply beneath that post. Normal channel bindings choose the agent and session; the plugin does not override session scope.

For multiple bots, put account-specific values under channels.x.accounts.<accountId>. Root fields are shared defaults; the default account ID is default.

Manage the allowlist

Open X replies in the Control UI as an administrator. The page shows the effective union of config allowFrom entries and users added through the page. Add a username to resolve it to a stable numeric X user ID. Stored entries retain the resolved username, display name, adding operator, and timestamp. Remove a stored entry from the page; config entries are read-only and must be removed from config.

A locally linked installation uses the Custom plugin UI setting. Enable Settings → Labs → Custom plugin UI, then use the Control UI served by the Gateway over HTTPS or trusted loopback. Bundled installations do not need that setting.

The Gateway methods x.allowlist.list, x.allowlist.add, and x.allowlist.remove require operator.admin. Authorize senders by numeric ID, either 987654321 or x:987654321; handles belong in the add-by-handle UI, not in allowFrom.

groupPolicy: "open" allows any author whose post reaches the mention feed and emits a security warning. Keep allowlist for a maintainer bot. disabled turns off inbound turns. dmPolicy accepts only disabled.

Event modes

Mode Behavior
auto Uses the Activity API when a bearer token is configured and subscriptions succeed; otherwise polls mentions.
stream Requests Activity API streaming with the app-only bearer token; falls back to polling when no bearer token is configured.
poll Polls the mentions endpoint using the user-context token.

The default is auto. Streaming ensures a post.mention.create subscription for the bot, ignores blank keep-alives, and reconnects with backoff after a stalled or disconnected stream. Each connection runs a mentions backfill from the saved cursor; post IDs deduplicate stream and polling events. An Activity API 403 switches to polling and reports:

text
activity API unavailable for this app; polling

Polling defaults to 60 seconds; events.pollSeconds cannot be less than 15. Inbound posts are durably queued before the cursor advances. Completed event IDs are retained for up to 30 days with a limit of 2,000 completed entries per account, preventing duplicate turns after reconnects and restarts while those entries remain retained.

Thread context and replies

The plugin renders available thread posts oldest first as @handle (time): text and marks the triggering mention. It follows reply ancestors, reads the recent conversation, and includes quoted posts. threadContext.maxPosts defaults to 50; the root and newest posts are kept when the limit is reached. Recent-search coverage is limited to seven days, and unavailable or deleted posts cannot be included.

Replies are split into a self-reply chain with at most 280 weighted characters per post; each URL counts as 23 characters. The last chunk receives replySignature, whose default is 🤖 automated reply. Set it to an empty string to disable the signature.

When the agent starts a visible work session, its first session URL is appended to the reply unless the text already contains that URL. This is a public link in a public reply and uses X's URL-containing reply price.

For direct replies through the message tool or CLI, target the post ID with an x: prefix or its full X status URL:

bash
openclaw message send --channel x --target x:1234567890123456789 --message "Reply text"

The target must satisfy X's reply eligibility: its author mentioned or quoted the app account. Sending media or creating an original post is unsupported.

Costs

Operation X API price
Post read $0.005 per returned post, deduplicated per UTC day
Reply without a URL $0.015 per reply post
Reply containing a URL $0.20 per reply post
Username lookup $0.01 per lookup
Empty mentions poll No post-read charge

Thread expansion reads additional posts. A long answer creates multiple billed reply posts. Adding a handle in the allowlist UI performs a paid username lookup. These are X API costs, separate from the agent's model usage.

When an Activity event omits mention entities, the plugin looks up the post to verify that it targets this bot before queueing it. Events for another bot do not advance this account's cursor. If a mention entity provides only a username, the plugin performs a $0.01 user lookup to verify the numeric recipient ID; configured usernames alone cannot authorize a reply.

Configuration reference

These fields work at channels.x and on individual account entries unless noted.

Field Default Purpose
enabled true Enables the channel or account.
name Unset Optional account display name.
userId Required Numeric user ID of the bot account.
username Required Bot username without @.
clientId Required OAuth2 confidential application client ID.
clientSecret Required Application secret; supports SecretRef.
refreshToken Required Bot's user-context OAuth2 refresh token; supports SecretRef.
bearerToken Unset App-only Activity API bearer token; supports SecretRef.
events.mode auto auto, stream, or poll.
events.pollSeconds 60 Mentions polling interval, minimum 15 seconds.
allowFrom [] Numeric author IDs, optionally prefixed with x:.
groupPolicy allowlist allowlist, open, or disabled.
dmPolicy disabled Only disabled is accepted.
threadContext.maxPosts 50 Maximum posts included in agent thread context, from 2 to 100.
replySignature 🤖 automated reply Added to the last reply chunk; up to 140 characters, empty disables it.
accounts Unset Named account overrides; channel root only.
defaultAccount default Account selected when none is specified; channel root only.

Troubleshooting

No reply: check the account's status, numeric bot ID, and effective allowlist. The dropped-mention counter and last dropped author explain intentional silence. There is no pairing flow. An empty allowlist blocks all authors under the default policy.

Streaming falls back: the Activity API is unavailable for the app, or no app-only bearer token was supplied in auto or stream mode. Polling remains operational; check the reported event mode, stream connection/backoff, last event, and cursor.

Token refresh fails: check the client ID, client secret, refresh token, and granted OAuth2 scopes. Status reports refresh state without exposing secrets.

Reply rejected: confirm the source author mentioned or quoted the app account and the app has tweet.write. Inspect the error before retrying a partially sent reply chain.

Failures before a reply POST can retry safely. If a POST's outcome is uncertain, OpenClaw keeps that uncertainty instead of automatically sending the reply again. Check X before manually retrying an uncertain or partially sent reply.

Was this useful?