Skip to content

Documentation navigation

Discord triggers

Start an agent from signed Discord interactions and Webhook Events.

#Start your agent from Discord

A Discord connection receives signed HTTP requests from your Discord application and starts an agent running in Studio or in your own terminal. Choose the protocol that matches the setting you are configuring in Discord:

Connection protocolDiscord settingWhat can start an agent
InteractionsGeneral Information → Interactions Endpoint URLCommands, message components and modal submissions
Webhook EventsWebhooks → Endpoint URLSupported application, entitlement, lobby and Social SDK direct-message events

Ordinary server messages and Gateway events use Discord's WebSocket transport. They are separate from these signed HTTP connections.

#Try a synthetic connection first

You can test the entire local path without a Discord account or a paid service. In a platform source checkout, open a terminal in tooling/mock-gateway:

Shell
uv run --frozen maivn-mock-gateway replay-discord --print-setup

This prints a synthetic application ID and public key. That key is public test material: use it for a separate local fixture connection. A real Discord app must use the public key Discord assigned to that app.

In Studio's trigger setup, choose Add a connection. Or open Integrations → Connections in the Developer Portal and add a connection there. Choose Discord signed HTTP events, select Interactions, and paste the synthetic application ID and public key. Save the connection ID and callback address.

Without either UI, use the signed-in connection API described in Terminal connection setup. Send this JSON to POST /v1/projects/{project_id}/connections, replacing the application and key values with the command's output:

JSON
{
  "display_name": "Local Discord replay",
  "setup_kind": "discord_webhook",
  "discord_protocol": "interactions",
  "discord_application_id": "123456789012345678",
  "discord_public_key": "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8"
}

Ed25519 uses a public verification key. This connection does not ask you for a bot token or return a signing secret.

#Declare and start the agent

Add a declaration to the module containing your configured Agent. Replace the connection ID and target with values from your project:

Python
discord_trigger = (
    agent.on_connection("conn_your_discord", event_type="discord.interaction.command")
    .key("discord-command")
    .named("Discord command")
    .target("your-agent-id", version=1)
    .asking("Handle this Discord command: {{event.payload.data.name}}")
    .display_in_app()
)

In Studio, reload the app and follow the trigger's checklist to start serving and turn it on. Without Studio, start maivn serve with the same project, agent ID and version. Wait for Announced, then call discord_trigger.invoke() once from a registration script in another terminal. Keep the serving terminal open. Do not register on every import of the module.

#Send a local command

From tooling/mock-gateway, substitute the connection ID you just created:

Shell
uv run --frozen maivn-mock-gateway replay-discord --connection-id conn_your_discord --kind interaction-ping
uv run --frozen maivn-mock-gateway replay-discord --connection-id conn_your_discord --kind command --delivery-id 123456789012345682 --command-name triage --repeat 2

The PING checks the connection's signed handshake. The command sends the same signed fixture twice. Both receipts should report protocol_acknowledged: true. Then open the trigger's run history and verify one completed run. An accepted callback alone does not prove the agent ran.

Keep the same delivery ID when retrying a command. Use a new ID to represent a new command. The replay tool contacts loopback only and does not change your connection, registration or Discord settings.

For Webhook Events, print the appropriate setup and create a separate connection with protocol webhook_events:

Shell
uv run --frozen maivn-mock-gateway replay-discord --print-setup --kind webhook-event
uv run --frozen maivn-mock-gateway replay-discord --connection-id conn_your_webhook_events --kind webhook-ping
uv run --frozen maivn-mock-gateway replay-discord --connection-id conn_your_webhook_events --kind webhook-event --event-type ENTITLEMENT_CREATE --delivery-id 123456789012345683 --repeat 2

Declare its trigger with event_type="discord.webhook.entitlement_create". Webhook identity includes the event type and event time as well as its data. Keep these unchanged when replaying the same event.

#Connect a real Discord application

In the Discord Developer Portal, select your application and copy Application ID and Public Key from General Information. Create a new mAIvn connection with those values and the intended protocol. Local synthetic values cannot verify requests from this application.

For local testing, run the free receiver-only tunnel with that connection:

Shell
maivn dev-tunnel --connection-id conn_your_real_discord

Copy its HTTPS callback into the matching Discord setting from the table above. Keep the tunnel and agent running. A new temporary tunnel has a new address, so update Discord when you restart it. See Local webhook tunnels for installation and operation.

For Interactions, register the commands your Discord application should offer and invoke one in a context where it is installed. mAIvn acknowledges supported commands, components and modal submissions with an ephemeral receipt after durable admission. The agent's result appears in mAIvn, or at the trigger's separately configured output destination. Autocomplete requests receive an empty choice list. This connection does not generate autocomplete suggestions.

For Webhook Events, enable events, select the types you need, and save. Use the matching lower-case discord.webhook. event name in your declaration. For example, Discord's ENTITLEMENT_UPDATE maps to discord.webhook.entitlement_update.

Discord verifies the callback with a signed PING and checks that invalid signatures are rejected. A correct public key, matching application ID and the correct protocol are all required. HTTP interactions and Webhook Events use different PING and acknowledgement shapes. Switching only the trigger name does not switch a connection's protocol.

#Send the result to a Discord channel

Create a webhook for the destination channel in Discord, then add a Discord output destination connection in Studio or the Developer Portal. Paste its webhook URL there. The saved connection keeps the URL private.

For terminal setup, use the same connection API with setup_kind: "discord_output", a display_name, and endpoint_url set to your Discord webhook URL. Keep that URL out of source control and terminal history.

Replace .display_in_app() in the declaration with:

Python
discord_trigger = discord_trigger.to_connection("conn_your_discord_output")

Reload the app and apply the updated trigger in Studio, or invoke the declaration from your registration script. The incoming connection verifies the event. The output connection selects the channel receiving the completed result. The initial Discord interaction receipt remains a brief acknowledgement.

#Troubleshooting

What you seeWhat to check
Discord cannot save the endpointUse the current public HTTPS callback, the app's own public key and application ID, and the matching connection protocol.
A signed local replay returns 401Its connection must use the synthetic public key from --print-setup, not a real app's key. Revoked or unknown connections also refuse it.
A callback is accepted but no run appearsCheck that the trigger was enabled before the event, names the exact connection/event type, and targets an agent that is serving.
An accepted callback waits during an outageThe durable dispatch retries automatically. Restore the local planes; a retry keeps its original event identity.
A repeated command does not start another runReusing its delivery ID is deliberately deduplicated. Use a new ID for a new event.
A server message does not arriveOrdinary Gateway messages require a different transport. They are not Webhook Events.

Discord signature timestamps may be up to 660 seconds old, covering its documented ten-minute webhook retry period plus clock skew. Future timestamps are limited to 60 seconds. Event identities remain deduplicated beyond that signature window. Other providers retain their existing signature rules.

Provider details: Discord Webhook Events and receiving interactions.