X (Twitter) direct messages¶
Answer direct messages sent to an X account. It needs X's paid Basic API tier or higher for DM access, and you bring your own X developer app (an OAuth 2.0 client id and secret).
How it fits together¶
- Inbound is polled. X's DM endpoints have no delivery mechanism at the API tiers a self-service connection can
reasonably ask for, so core polls
GET /2/dm_eventsevery 60 seconds for every tenant with a connected account (20 seconds after start-up). One replica polls at a time, because the poll position is advanced without concurrency control. Expect a reply within a minute or two, not instantly. - Outbound replies go to the same DM conversation through the approval gate as
channel.x.send. - A conversation is keyed by the X DM conversation id.
Set it up (two steps)¶
- In the X developer portal create a project and app. Under User
authentication settings turn on OAuth 2.0, set the app type to Confidential client, and request the scopes
dm.read,dm.write,tweet.read,users.readandoffline.access. Add the platform's callback URL as a Callback URI: it is<app.x.oauth.redirect-uri>, the same value for every tenant, so ask your platform operator. - In the console open Channels > X - DMs: enter the Client ID and Client Secret and Register app (the secret is stored encrypted), then Connect X Account to approve access on X. X sends you back to the console's Channels page when you finish.
Operator settings¶
| Property | Meaning |
|---|---|
app.x.oauth.redirect-uri |
The callback URL registered with X (path /oauth/x/callback on core's public address). |
app.x.oauth.console-redirect-uri |
Where X sends the browser after consent. Defaults to <app.web.public-url>/admin/channels; with neither set, the callback refuses with a message naming both. |
Security¶
The consent flow carries the tenant and the PKCE verifier in an encrypted state value, which expires after ten
minutes and is single-use. The browser that completes consent is not proven to be the one that started it unless
app.oauth.state.require-browser-cookie is on (off by default); see
security hardening for the trade-off.
Access and refresh tokens are stored encrypted.