OpenClaw Discord Activities
Self-contained HTML widgets launched inside Discord, the tunnel and developer-portal setup, the OAuth and instance checks, expiry rules, and the troubleshooting list
Sometimes a chart or a small interactive form says more than a paragraph in Discord. OpenClaw’s Discord Activities let an agent post an interactive, self-contained HTML widget to the current channel; the message carries an open-widget button, and clicking it launches the widget inside Discord. The feature is off by default: the widget tool stays core-owned, and only when an activities block is present and a client secret resolves do the Activity routes, the launch handler and the current-channel presenter become available behind it. Here are the prerequisites, the setup, what core does with the widget, the security model, and the troubleshooting list.
Prerequisites and setup
- You need an existing Discord bot, a public HTTPS hostname that reaches the Gateway, and permission to configure Activities and OAuth2 for the bot’s application; any HTTPS reverse proxy or tunnel works, and the docs use a named Cloudflare Tunnel that gives a stable hostname without exposing the Gateway port, with normal Gateway authentication kept on because only the Activity prefix is public.
- Expose the Gateway over HTTPS and verify the Activity path reaches it once the config is added; then in the developer portal enable Activities and create a URL mapping with the root prefix and the public hostname plus the activity path as target, without a trailing slash.
- Copy the OAuth2 client secret, adding a loopback placeholder redirect URI if the application has none since Discord requires one while the embedded SDK handles the return flow, and treat the secret as a credential: never in chat, logs or a committed config file.
- Add one activities block to the Discord account with the client secret and an optional application id that defaults to the bot application learned at startup; the secret can come from an environment variable but the block must remain present to opt in, and the normal access settings stay separate, so the DM allowlist controls who can DM the agent, not who can open a widget already posted in a channel. Config changes hot-reload; a service environment change needs a restart.
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.
What core does with a widget
Core validates and wraps the widget document before handing it to Discord: the presenter accepts HTML source up to 48 kibibytes, stores the canonical composed document, and always labels the button open widget; the standard widget fields for pin, name, tab, size, frame, ordering and capabilities remain available because dashboard state stays core-owned, and registered non-HTML widget kinds are not offered when Discord is the only presentation route. Widget network requests are intentionally blocked, so all CSS, JavaScript, images and data must be inlined.
The security model
- OAuth identifies the Discord user before widget metadata is returned, Discord’s activity-instance API must confirm the user is present in the current instance and the instance channel must match the channel where the widget was posted, and everyone Discord permits into that channel can open its widgets, so narrowing the audience is done with Discord channel permissions rather than OpenClaw allowlists.
- OAuth sessions expire after fifteen minutes, widget document capabilities after sixty seconds and work once, widgets expire after seven days with at most sixty-four retained per plugin instance, and the sandboxed iframe with a script-only sandbox and a network-blocking content security policy is defence in depth rather than a boundary against the agent that authored the widget.
- When Activities is disabled or credentials are missing, the route stays registered internally but public requests under the activity prefix return the normal 404, and the public shell and token-exchange route never expose widget HTML without a valid OAuth session and a one-time capability.
OpenClaw on Discord is the channel this presenter belongs to, and OpenClaw behind Cloudflare the tunnel pattern the docs use to publish the one public prefix.
Troubleshooting
A gateway-offline message means the tunnel is not routing to the actual bind port, the portal target lacks the activity path, the config has not reloaded, or the token and secret do not both resolve. A blank page or a CSP block means the mapping added a second activity segment or the shell, its script and the SDK module are not all returning through the Discord proxy. Widget-unavailable means the button was launched outside the channel it was posted in, though server-side launch records usually resolve the exact widget even when Discord mangles the button id. And Discord does not launch Activities from forum-style channels, so OpenClaw refuses the component there rather than posting a dead button. OpenClaw remote access covers the general rules for exposing a Gateway path, and The OpenClaw Gateway explained the process the Activity route lives in.
On Diali
On Diali the Discord bot is connected from the dashboard, and the Activity route would need a public HTTPS hostname on the assistant’s Gateway, which is not exposed today, so the feature stays self-run for now. Hosted OpenClaw on Diali is the assistant and Diali security describes what the Gateway does and does not expose.
- One block, one secret, one public prefix.
- OAuth, instance membership, channel match, one-time capability.
- Fifteen minutes, sixty seconds, seven days.
Stop reading about it, build one
Set up an agent, pick a channel, and have it working inside the app you already keep open.
