Bug type
Regression (worked before, now fails)
Summary
When channels.slack.mode is set to http, OpenClaw cannot complete the Slack url_verification handshake because Slack sends this initial challenge without a signature, but OpenClaw rejects unsigned requests, making HTTP mode permanently non-functional.
Steps to reproduce
- Set
channels.slack.mode: "http" in openclaw.json and configure signingSecret
- In Slack App settings, under Event Subscriptions, enter your webhook URL (e.g. via ngrok)
- Slack sends a POST to the webhook URL with
{"type": "url_verification", "challenge": "..."}
- This request has no
X-Slack-Signature header (Slack does not sign the initial challenge)
- OpenClaw either rejects it or fails to respond with the challenge value
- Slack marks the URL as invalid — HTTP mode never activates
- Gateway logs show the config change was accepted (
config hot reload applied) but the handshake never completes
Expected behavior
OpenClaw responds to Slack's url_verification challenge with the {"challenge": "..."} value, completing the handshake and activating HTTP webhook mode.
This is consistent with how Slack's own official SDKs (bolt-js, python-slack-sdk) handle it — they explicitly whitelist url_verification from HMAC signature checks, since Slack does not sign this one request type by design.
Actual behavior
Slack rejects the webhook URL as invalid. HTTP mode never activates. OpenClaw silently falls back to or remains in Socket Mode with no error surfaced to the user.
OpenClaw version
2026.3.2
Operating system
macOS Darwin 25.3.0 arm64
Install method
npm global (node v25.6.1)
Logs, screenshots, and evidence
Gateway log showing config accepted but handshake never completing:
[INFO] config hot reload applied (channels.slack.mode)
[INFO] slack socket mode listening at /slack/events
No subsequent log entry from Slack confirming the url_verification challenge was answered. Slack Event Subscriptions UI shows the URL as unverified/failed.
Impact and severity
Affected: Any user trying to use Slack HTTP webhook mode (Events API) instead of Socket Mode
Severity: High — HTTP mode is completely non-functional; blocks a critical stability improvement
Frequency: 100% reproducible — Slack always sends url_verification unsigned by design
Consequence: Users are forced to use Socket Mode, which has periodic WebSocket drops (every ~90 min via the internal health-monitor stale-socket restarts). Messages sent during reconnect windows (~1–2 sec) are silently lost. HTTP mode would eliminate this entirely — no persistent connection, no drops, no lost messages.
Additional information
Workaround in place: Socket Mode + custom watchdog script (com.sky.slack-watchdog launchd agent) that detects pong timeouts and restarts the gateway within 2 minutes. This reduces max downtime but does not eliminate dropped messages.
Root cause reference: Slack's own documentation states url_verification is intentionally sent unsigned: https://api.slack.com/events/url_verification
Bolt.js source for comparison (skips signature check for url_verification): https://github.com/slackapi/bolt-js/blob/main/src/receivers/ExpressReceiver.ts
Suggested fix: In the HTTP webhook handler, check if req.body.type === 'url_verification' before running signature verification. If so, respond immediately with {"challenge": req.body.challenge} and skip the HMAC check for this one event type only.
Bug type
Regression (worked before, now fails)
Summary
When
channels.slack.modeis set tohttp, OpenClaw cannot complete the Slackurl_verificationhandshake because Slack sends this initial challenge without a signature, but OpenClaw rejects unsigned requests, making HTTP mode permanently non-functional.Steps to reproduce
channels.slack.mode: "http"in openclaw.json and configuresigningSecret{"type": "url_verification", "challenge": "..."}X-Slack-Signatureheader (Slack does not sign the initial challenge)config hot reload applied) but the handshake never completesExpected behavior
OpenClaw responds to Slack's
url_verificationchallenge with the{"challenge": "..."}value, completing the handshake and activating HTTP webhook mode.This is consistent with how Slack's own official SDKs (bolt-js, python-slack-sdk) handle it — they explicitly whitelist
url_verificationfrom HMAC signature checks, since Slack does not sign this one request type by design.Actual behavior
Slack rejects the webhook URL as invalid. HTTP mode never activates. OpenClaw silently falls back to or remains in Socket Mode with no error surfaced to the user.
OpenClaw version
2026.3.2
Operating system
macOS Darwin 25.3.0 arm64
Install method
npm global (node v25.6.1)
Logs, screenshots, and evidence
Impact and severity
Affected: Any user trying to use Slack HTTP webhook mode (Events API) instead of Socket Mode
Severity: High — HTTP mode is completely non-functional; blocks a critical stability improvement
Frequency: 100% reproducible — Slack always sends url_verification unsigned by design
Consequence: Users are forced to use Socket Mode, which has periodic WebSocket drops (every ~90 min via the internal health-monitor
stale-socketrestarts). Messages sent during reconnect windows (~1–2 sec) are silently lost. HTTP mode would eliminate this entirely — no persistent connection, no drops, no lost messages.Additional information
Workaround in place: Socket Mode + custom watchdog script (
com.sky.slack-watchdoglaunchd agent) that detects pong timeouts and restarts the gateway within 2 minutes. This reduces max downtime but does not eliminate dropped messages.Root cause reference: Slack's own documentation states url_verification is intentionally sent unsigned: https://api.slack.com/events/url_verification
Bolt.js source for comparison (skips signature check for url_verification): https://github.com/slackapi/bolt-js/blob/main/src/receivers/ExpressReceiver.ts
Suggested fix: In the HTTP webhook handler, check if
req.body.type === 'url_verification'before running signature verification. If so, respond immediately with{"challenge": req.body.challenge}and skip the HMAC check for this one event type only.