Discord
ChannelRun your agent as a Discord bot across servers and threads.
At a glance
From Eve docs · synced Jul 22, 2026
Webhook route
POST /eve/v1/discordExtension hooks
onCommandEnvironment variables
- DISCORD_PUBLIC_KEY
- verifies X-Signature-Ed25519 + X-Signature-Timestamp
- DISCORD_APPLICATION_ID
- edits the deferred response and sends followups
- DISCORD_BOT_TOKEN
- proactive messages + fallback + typing indicators
Capabilities
- Human-in-the-loop — HITL renders as Discord components.
- Proactive sessions — Start a session without an inbound interaction through receive(discord, { message, target, auth }) from a schedule run handler, or args.receive(discord, ...) from another channel.
- Attachments — Inbound file attachments are not supported on this channel today.
Derived from Eve's Discord docs.
Install
Install the framework. The Discord channel ships with it:
npm install eve@latestQuick start
Create agent/channels/discord.ts:
// agent/channels/discord.tsimport { discordChannel } from "eve/channels/discord";export default discordChannel({ botToken: () => process.env.DISCORD_BOT_TOKEN!, publicKey: () => process.env.DISCORD_PUBLIC_KEY!,});Configure
Create a Discord application, add a bot, and set the interactions endpoint URL to the route eve serves (/eve/v1/discord). Provide the bot token and public key through environment variables. See the Discord channel docs for intents and slash-command setup.
How it behaves
Excerpts from Eve's Discord docs. Prefer the source when something looks out of date.
Dispatch
onCommand(ctx, interaction) decides whether to dispatch and under what auth. Return { auth } to proceed or null to drop the interaction. By default, auth comes from the invoking user. Event handlers receive (eventData, channel, ctx), with Discord platform handles on channel.discord:
import { discordChannel } from "eve/channels/discord";
export default discordChannel({
onCommand: (ctx, interaction) => ({
auth: {
principalId: interaction.user.id,
principalType: "user",
authenticator: "discord",
attributes: { channel_id: interaction.channelId, guild_id: interaction.guildId ?? "" },
},
}),
events: {
"message.completed"(eventData, channel, ctx) {
if (eventData.finishReason === "tool-calls") return;
if (eventData.message) channel.discord.post(eventData.message);
},
},
});
Delivery
The default message.completed handler edits the deferred response for the first reply and sends followups after that. If the interaction token is rejected, it falls back to a bot-authenticated channel message. Long text is split to Discord's 2000-char limit, and generated messages default to allowed_mentions: { parse: [] }.
Typing fires on turn.started and actions.requested, but only when a bot token is present. In custom hooks, call channel.discord.startTyping() yourself.
Human-in-the-loop (HITL)
HITL renders as Discord components. Confirmations and options become buttons, display: "select" becomes a string select, and freeform input becomes a button that opens a modal. When the user responds, the parked session (paused awaiting input) resumes.
Proactive sessions
Start a session without an inbound interaction through receive(discord, { message, target, auth }) from a schedule run handler, or args.receive(discord, ...) from another channel. The proactive target shape is { channelId, conversationId?, initialMessage? }. Either path needs DISCORD_BOT_TOKEN.
Attachments
Inbound file attachments are not supported on this channel today.