Twitch chat support over Twitch's chat (IRC) interface via the Twurple client. Apus signs in as a Twitch bot account, joins one channel per configured account, and replies in that channel.
Install
Twitch ships as an official plugin; it is not part of the core install.
npm registry
```bash
apus plugins install @openclaw/twitch
```
Local checkout
```bash
apus plugins install ./path/to/local/twitch-plugin
```
plugins install registers and enables the plugin. Picking Twitch during apus onboard or apus channels add installs it on demand. Use the bare package name to follow the current release; pin an exact version only for reproducible installs. Requires Apus 2026.4.10 or newer.
Details: Plugins
Quick setup
Install the plugin
See [Install](#install) above.
Create a Twitch bot account
Create a dedicated Twitch account for the bot (or use an existing account).
Generate credentials
Use [Twitch Token Generator](https://twitchtokengenerator.com/):
- Select **Bot Token**
- Verify scopes `chat:read` and `chat:write` are selected
- Copy the **Client ID** and **Access Token**
Find your Twitch user ID
Use [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/) to convert a username to a Twitch user ID.
Configure the token
- Env: `APUS_TWITCH_ACCESS_TOKEN=...` (default account only)
- Or config: `channels.twitch.accessToken`
If both are set, config takes precedence (the env var is only a fallback for the default account).
Start the gateway
```bash
apus gateway run
```
Add access control (allowFrom or allowedRoles) to prevent unauthorized users from triggering the bot. requireMention defaults to true.
Minimal config:
{
channels: {
twitch: {
enabled: true,
username: "apus", // Bot's Twitch account (authenticates)
accessToken: "oauth:abc123...", // OAuth access token (or use APUS_TWITCH_ACCESS_TOKEN env var)
clientId: "xyz789...", // Client ID from Token Generator
channel: "yourchannel", // Which Twitch channel's chat to join (required)
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
},
},
}
What it is
- A Twitch channel owned by the Gateway.
- Deterministic routing: replies always go back to the Twitch channel the message came from.
- Each joined channel maps to an isolated group session key
agent:<agentId>:twitch:group:<channel>. usernameis the bot's account (who authenticates),channelis which chat room to join. One account entry joins exactly one channel.- Tokens work with or without the
oauth:prefix; Apus normalizes both ways (the setup wizard expects theoauth:form).
Inbound durability
Apus durably queues each accepted Twitch chat message before normal dispatch. Pending or retryable messages survive a Gateway restart, stay serialized for the configured channel, and use Twitch's message ID to suppress duplicate queue entries while the active or retained completion record exists.
Twitch chat does not replay a PRIVMSG after the client has accepted it. This protects the local accept-to-dispatch crash window, but it cannot recover messages missed before durable admission. If the queue append itself fails, Apus logs the failure; reconnecting does not ask Twitch to resend that message.
Token refresh (optional)
Tokens from Twitch Token Generator cannot be refreshed by Apus - regenerate when expired (they last a few hours; no app registration needed).
For automatic refresh, create your own app at the Twitch Developer Console and add:
{
channels: {
twitch: {
username: "apus",
accessToken: "oauth:abc123...",
clientId: "xyz789...",
channel: "yourchannel",
clientSecret: "your_client_secret",
refreshToken: "your_refresh_token",
},
},
}
With both set, the plugin uses a refreshing auth provider that renews tokens before expiration and logs each refresh. Without refreshToken it logs token refresh disabled (no refresh token); without clientSecret it falls back to a static (non-refreshing) token.
Multi-account support
Use channels.twitch.accounts with per-account credentials. See Configuration for the shared pattern.
Example (one bot account in two channels):
{
channels: {
twitch: {
accounts: {
channel1: {
username: "apus",
accessToken: "oauth:abc123...",
clientId: "xyz789...",
channel: "yourchannel",
},
channel2: {
username: "apus",
accessToken: "oauth:def456...",
clientId: "uvw012...",
channel: "secondchannel",
},
},
},
},
}
Every account entry needs its own accessToken (the env var covers only the default account). An account joins exactly one channel, so joining two channels means two accounts. channels.twitch.defaultAccount picks which account is the default.
Access control
allowFrom is a hard allowlist of Twitch user IDs. When it is set, allowedRoles is ignored; leave allowFrom unset to use role-based access instead.
In the setup wizard, Disabled blocks all chat by writing an empty allowFrom list. Open removes the user ID allowlist and allows all roles. Allowlist keeps a nonempty user ID list; otherwise, it removes the empty override and allows moderators and VIPs. All three choices require an @mention.
Choosing Open or Disabled discards any previous user IDs. Switching back to Allowlist does not restore them; configure allowFrom again to restrict access to specific IDs.
Existing configurations with no allowFrom and empty or absent allowedRoles remain open. There is no automatic migration because those values do not record whether Disabled was intended. Reselect Disabled in setup or configure allowFrom: [] to block chat.
Available roles: "moderator", "owner", "vip", "subscriber", "all".
User ID allowlist (most secure)
```json5
{
channels: {
twitch: {
accounts: {
default: {
username: "apus",
accessToken: "oauth:abc123...",
channel: "yourchannel",
allowFrom: ["123456789", "987654321"],
},
},
},
},
}
```
Role-based
```json5
{
channels: {
twitch: {
accounts: {
default: {
username: "apus",
accessToken: "oauth:abc123...",
channel: "yourchannel",
allowedRoles: ["moderator", "vip"],
},
},
},
},
}
```
Disable @mention requirement
By default, `requireMention` is `true`. To respond to all allowed messages:
```json5
{
channels: {
twitch: {
accounts: {
default: {
username: "apus",
accessToken: "oauth:abc123...",
channel: "yourchannel",
requireMention: false,
},
},
},
},
}
```
Why user IDs? Usernames can change, allowing impersonation. User IDs are permanent.
Find yours with the username to ID converter.
Troubleshooting
Execution identity audit
With execution identity collection enabled, a trusted native Twitch plugin attributes the run to the native Twitch sender, scoped to the configured account. The audit stores an opaque identity, not the raw Twitch user ID. Roles such as moderator authorize access; they do not replace the sender's identity. Missing native user IDs remain unknown even when a role or open policy allows a reply. External plugin installations do not gain trusted participant evidence through this path. Audit collection never changes these access decisions.
Connection and replies
First, run diagnostic commands:
apus doctor
apus channels status --probe
Bot does not respond to messages
- **Check access control:** Ensure your user ID is in `allowFrom`, or temporarily remove `allowFrom` and set `allowedRoles: ["all"]` to test.
- **Check the mention gate:** With `requireMention: true` (default), messages must @mention the bot username.
- **Check the bot is in the channel:** The bot only joins the channel named in `channel`.
Token issues
"Failed to connect" or authentication errors:
- Verify `accessToken` is the OAuth access token value (the `oauth:` prefix is optional)
- Check the token has `chat:read` and `chat:write` scopes
- If using token refresh, verify `clientSecret` and `refreshToken` are set
Token refresh not working
Check logs for refresh events:
```text
Using env token source for mybot
Access token refreshed for user 123456 (expires in 14400s)
```
If you see `token refresh disabled (no refresh token)`:
- Ensure `clientSecret` is provided
- Ensure `refreshToken` is provided
Config
Account config
username string
Bot username (the authenticating account).
accessToken string
OAuth access token with chat:read and chat:write (config or env for the default account).
clientId string
Twitch Client ID (from Token Generator or your app). Optional in the schema but required to connect.
channel string
Channel to join.
enabled boolean
Enable this account.
clientSecret string
Optional: for automatic token refresh.
refreshToken string
Optional: for automatic token refresh.
expiresIn number
Token expiry in seconds (refresh tracking).
obtainmentTimestamp number
Timestamp when the token was obtained (refresh tracking).
allowFrom string[]
User ID allowlist. When set, roles are ignored.