Overview
The Discord transport (internal/transport/discord/) speaks the Discord Gateway v10 protocol directly — no third-party SDK. WebSocket for inbound (Identify → Ready → Heartbeat/Ack → Dispatch(MESSAGE_CREATE)); REST for outbound (POST /channels/{id}/messages).
Prerequisites
- A Discord Application with a Bot user. Create at https://discord.com/developers/applications → New Application → Bot tab → Add Bot.
- A bot token (Bot tab → Reset Token → copy the token — you only see it once).
- Message Content intent enabled (Bot tab → Privileged Gateway Intents). Without this, the Gateway strips message text from every event and rousseau will see empty bodies.
- The bot invited to at least one server (or DMs enabled). Generate the invite URL under OAuth2 → URL Generator with the
botscope andSend Messages+Read Message Historypermissions.
Configuration
discord:
token: "Bot MTIz..."
reply_header: ""
allowlist:
- "123456789012345678"
| Field | Default | Effect |
|---|---|---|
token |
required | Bot token from the Developer Portal. |
reply_header |
empty | Prepended to every outbound reply. |
allowlist |
[] |
Discord user IDs whose messages are handled. |
Command-line
rousseau discord --token 'MTIz...' --allow 123456789012345678
Gateway intents
rousseau requests three intents (internal/transport/discord/client.go):
| Intent | Bit | Purpose |
|---|---|---|
GUILD_MESSAGES |
1 << 9 |
Messages in server channels. |
DIRECT_MESSAGES |
1 << 12 |
DMs to the bot. |
MESSAGE_CONTENT |
1 << 15 |
Populates the content field. Must be enabled in the portal. |
Without the Message Content intent, MESSAGE_CREATE events arrive with empty content and rousseau will log discord.empty_body.
Heartbeat
The transport honours the Gateway's heartbeat_interval from the Hello opcode, sending Heartbeat + tracking heartbeat_ack. Missed acks close the socket and let systemd restart the process.
Reply header
Discord renders **text** as bold and does not require any specific header shape. Override as needed:
discord:
reply_header: "**Rousseau Agent**\n"
Permissions bit calculator
Discord uses a bitmask to encode a bot's channel permissions. Each permission is a power of 2. Common ones for rousseau:
| Permission | Bit |
|---|---|
| Read Messages / View Channels | 1 << 10 = 1024 |
| Send Messages | 1 << 11 = 2048 |
| Send Messages in Threads | 1 << 38 = 274877906944 |
| Read Message History | 1 << 16 = 65536 |
| Add Reactions | 1 << 6 = 64 |
To grant multiple permissions, OR the bits together and paste the resulting integer into the OAuth2 URL Generator's permissions= parameter:
Read Messages (1024) OR Send Messages (2048) OR Read Message History (65536) = 68608
Gateway lifecycle
The Gateway is stateful:
Client Discord Gateway
│
│ ──── Connect ────▶
│ ◀── HELLO (heartbeat_interval)
│
│ ───── IDENTIFY (token, intents) ────▶
│ ◀── READY (session_id, user)
│
│ ─── Heartbeat every N ms ─▶
│ ◀── HEARTBEAT_ACK
│
│ ◀── MESSAGE_CREATE (a user typed)
│ ─── (rousseau handles + POSTs reply)
│
│ ◀── Disconnect (code 4009: session timed out)
│ ─── RESUME (session_id) or re-IDENTIFY
The client tracks heartbeat_ack. If an ack is missed, the socket closes and the process exits — systemd or the container runtime restarts.
Failure modes
| Symptom | Fix |
|---|---|
| Bot sees empty messages | Enable Message Content intent in the developer portal. |
| Gateway closes with code 4004 | Invalid token. Regenerate. |
| Bot cannot see any channels | Confirm the OAuth2 invite included the bot scope. |
| 403 on send | Bot lacks Send Messages permission in that channel. |
| Code 4014 on Identify | Requested an intent your app is not approved for (usually Message Content on a 100+ server bot). Verify your bot. |
| Code 4009 (session timed out) | Normal after long idle. Rousseau reconnects transparently. |
Troubleshooting
Gateway 4013 (Invalid Intents)
You are requesting an intent bit that does not exist. This usually means a mismatch between the client library's intent constants and Discord's current intent map. Rousseau builds the intent bitmask in internal/transport/discord/client.go; upgrade to the latest release if you see 4013 after a Discord API change.
Bot receives events but does not respond
Allowlist mismatch. The --allow value must be the numeric Discord user ID (not username, not display name). Retrieve it in Discord: enable Developer Mode in User Settings > Advanced, then right-click a user > Copy User ID.
DMs work but guild channels do not
Missing GUILD_MESSAGES intent, or the bot has not been invited to the guild. Guild permissions are separate from DM permissions — the bot must have the Read Messages permission for the channel.
429 Too Many Requests on outbound
Discord enforces a global 50 req/s rate limit per bot, plus per-channel limits. Under sustained load, rousseau does not currently retry — the caller must back off. See Guides: Rate limits.
Bot online status flaps
Discord considers a bot offline after ~40s without a heartbeat. Log line discord.heartbeat_missed indicates a network problem or CPU-starved daemon. Verify the container has enough CPU allocated.
Related pages
- Getting Started: First Transport — end-to-end walkthrough.
- Configuration — the
discordconfig block. - Transports — sibling transports.
- Guides: Audit & Approval Policies — policy for Discord servers.
- Deployment — running Discord in a Podman container.
Further reading
internal/transport/discord/client.go— Gateway connection, heartbeat, event pump.internal/cli/discord.go— CLI wiring.internal/transport/router.go— allowlist enforcement.- Discord API docs: Gateway.
- Discord API docs: Permissions.