Discord Activities: Launch Interactive HTML Widgets in Discord

Learn how to enable Discord Activities in OpenClaw, allowing agents to post self-contained HTML widgets with an Open widget button. This guide covers prerequisites and setup for developers.

Read this when

  • Setting up or troubleshooting Discord Activity widgets

Discord Activities let an agent drop a self-contained, interactive HTML widget into the current Discord channel. The posted message carries an Open widget button, and selecting it starts the widget inside Discord.

This capability is disabled unless explicitly turned on. show_widget stays a single core-owned tool. When channels.discord.activities is in place and a client secret can be resolved, the Discord Activity routes, the launch-button handler, and the current-channel presenter all become available behind that tool. If the block is absent, requests aimed at the public Activity prefix look no different from any unregistered route. There is no Discord-specific widget tool or alias to rely on.

Prerequisites

  • a working OpenClaw Discord bot
  • a public HTTPS hostname that can reach the OpenClaw gateway
  • permission to configure Activities and OAuth2 for the bot's Discord application

Any HTTPS reverse proxy or tunnel will do. A named Cloudflare Tunnel gives you a stable hostname while keeping the gateway port from being exposed directly.

# ~/.cloudflared/config.yml
tunnel: openclaw-discord
credentials-file: /home/you/.cloudflared/TUNNEL-ID.json
ingress:
  - hostname: openclaw.example.com
    service: http://127.0.0.1:18789
  - service: http_status:404
cloudflared tunnel login
cloudflared tunnel create openclaw-discord
cloudflared tunnel route dns openclaw-discord openclaw.example.com
cloudflared tunnel run openclaw-discord

Leave normal gateway authentication on. Only the Activity prefix is exposed, and the plugin handles OAuth, Activity instance membership, channel binding, sessions, and one-time document capabilities on its own.

Setup

Expose the gateway over HTTPS

Get your tunnel or reverse proxy running, then confirm that https://openclaw.example.com/discord/activity/ reaches the gateway once Activities configuration is added. Swap the example hostname for your own.

Enable Activities in Discord

In the Discord Developer Portal, open the existing bot application. Go to Activities, enable Activities, and set up a URL mapping:

  • prefix: ROOT (/)
  • target: openclaw.example.com/discord/activity

The target combines the public hostname with /discord/activity, leaving off any trailing slash.

Copy the OAuth2 client secret

Head to OAuth2 in the Developer Portal. Discord insists on at least one redirect URI, so if the application has none, add a local placeholder such as the loopback address; the Embedded App SDK manages the Activity return flow. Copy or regenerate the application client secret. Treat it like a credential: never paste it into chat, logs, or a committed configuration file.

Configure OpenClaw

Add one block to the Discord account that should offer widgets:

{
  channels: {
    discord: {
      token: "${DISCORD_BOT_TOKEN}",
      activities: {
        clientSecret: "${DISCORD_CLIENT_SECRET}",
        // Optional. Defaults to the bot application ID learned at startup.
        applicationId: "YOUR_DISCORD_APPLICATION_ID",
      },
    },
  },
}

When DISCORD_CLIENT_SECRET is set, clientSecret can be left out of the block. The block itself must stay present to opt in.

Normal Discord access settings are untouched. For instance, allowFrom still governs who can DM the agent; it does not govern who can open a widget already posted in a channel.

Restart and test

Restart the gateway. In a Discord conversation, ask the agent to display an interactive widget. The agent invokes show_widget; then click Open widget on the posted message.

Core validates and wraps the widget document before handing it to Discord. The presenter accepts HTML source up to 48 KiB, stores the canonical composed document, and always labels the Activity button Open widget. The usual show_widget pin, name, tab, size, frame, ordering, and capability fields remain available because dashboard state stays core-owned. Registered non-HTML widget kinds are not offered when Discord is the only available presentation route.

Security model

  • OAuth identifies the Discord user before widget metadata is returned.
  • Discord's Get Activity Instance API must confirm that the OAuth user is present in the current Activity instance. The instance channel must match the channel where the widget was posted.
  • Everyone who Discord permits into that channel can open its widgets. To narrow the audience, use Discord channel permissions. OpenClaw command and DM allowlists do not grant or remove access to already-posted channel content.
  • OAuth sessions expire after 15 minutes. Widget document capabilities expire after 60 seconds and work once.
  • Widgets expire after seven days, with at most 64 retained per Discord plugin instance.
  • Widget HTML is authored by your agent and should be treated as trusted content. Do not embed secrets you would not want a buggy widget to expose.
  • The widget can navigate within its own nested frame. The sandbox="allow-scripts" iframe blocks top-level navigation, popups, and same-origin access, while its Content Security Policy blocks network connections and external resources. These controls are defense-in-depth, not a security boundary against the agent that authored the widget.
  • When Activities is disabled or its required account credentials are unavailable, the route remains registered internally but public requests under /discord/activity are left unhandled and return the normal 404.

The public Activity shell and token-exchange route become reachable through your tunnel when enabled. They do not expose widget HTML without a valid OAuth session and one-time document capability.

Troubleshooting

The Activity says “Gateway offline”

  • confirm the tunnel is running and routes to the gateway's actual bind port
  • confirm the Developer Portal target includes /discord/activity
  • restart the gateway after changing Discord or OpenClaw configuration
  • confirm the Discord bot token and Activities client secret both resolve in the running gateway; incomplete credentials keep /discord/activity externally hidden behind the normal 404

Discord opens a blank page or reports blocked:csp

  • verify the URL mapping uses ROOT and does not add a second /discord/activity segment
  • confirm the shell, shell.js, and SDK module all return through the Discord proxy
  • inspect gateway logs for requests under /discord/activity/

Widget network requests are intentionally blocked. Inline all CSS, JavaScript, images, and data needed by the widget.

“Widget unavailable”

Launch the button from the channel where the agent posted it. OpenClaw tracks launches server-side when clicked, so a fresh launch record can resolve the exact widget even when Discord omits or mangles the button's custom ID. When neither the custom ID nor a launch record resolves, OpenClaw opens the most recently posted live widget in that channel. Older widgets remain addressable through buttons that preserve their custom ID.

“You cannot launch Activities in this channel”

Discord does not launch Activities from forum-style channels. OpenClaw rejects the Activity component delivery there instead of posting a button that cannot work. Ask for the widget from a regular text channel instead.

1,056 words · updated Aug 22, 2026