zeroclaw-labs/zeroclaw · error · anyhow::Error
webhook channel requires a `secret` configured for request a
Error message
webhook channel requires a `secret` configured for request authentication; set [channels.webhook.{}].secret in config or remove the channel to silence this error What it means
WebhookChannel::listen refuses to start when [channels.webhook.<alias>] has no secret configured. This is a deliberate fail-closed guard: the inbound HTTP server accepts arbitrary POSTs, and without a secret it would have to accept unauthenticated requests from anyone. The channel errors immediately at startup, naming the alias and the exact TOML key to set, instead of silently running an open endpoint.
Source
Thrown at crates/zeroclaw-channels/src/webhook.rs:349
total_attempts,
err_msg,
delay.as_millis()
)
);
tokio::time::sleep(delay).await;
}
}
}
unreachable!("send loop exits via return or bail on the final attempt")
}
async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) -> Result<()> {
// Fail-fast: a webhook with no secret accepts *all* incoming requests,
// including unauthenticated ones. Refuse to start so the operator is
// forced to configure a secret.
if self.secret.is_none() {
anyhow::bail!(
"webhook channel requires a `secret` configured for request \
authentication; set [channels.webhook.{}].secret in config \
or remove the channel to silence this error",
self.alias,
);
}
use axum::{
Router,
body::Bytes,
extract::State,
http::{HeaderMap, StatusCode},
routing::post,
};
use portable_atomic::{AtomicU64, Ordering};
use std::sync::Arc;
let counter = Arc::new(AtomicU64::new(0));View on GitHub (pinned to 88bb9c8533)
Solutions
- Set [channels.webhook.<alias>].secret to a long random value: openssl rand -hex 32.
- Configure senders to that webhook to sign payloads with the same secret (x-webhook-signature: sha256=<hex HMAC-SHA256 of the raw body>).
- If the webhook channel is not actually needed, remove the whole [channels.webhook.<alias>] section so startup proceeds.
- If the secret lives in an env-specific overlay, verify the merged config actually contains the key for that alias (zeroclaw config dump).
Example fix
# before [channels.webhook.main] send_url = "https://example.com/hook" listen_path = "/hook" # listen() -> "webhook channel requires a `secret` configured ... set [channels.webhook.main].secret" # after [channels.webhook.main] send_url = "https://example.com/hook" listen_path = "/hook" secret = "9f2c..." # openssl rand -hex 32; senders must HMAC-SHA256-sign bodies with it
Defensive patterns
Strategy: validation
Validate before calling
// Before starting channels, assert the webhook alias has a non-empty secret.
fn validate_webhook_config(alias: &str, cfg: &WebhookChannelConfig) -> anyhow::Result<()> {
anyhow::ensure!(
cfg.secret.as_deref().map(|s| !s.trim().is_empty()).unwrap_or(false),
"[channels.webhook.{alias}] needs a non-empty `secret` (openssl rand -hex 32)"
);
Ok(())
} Try / catch
match webhook_channel.listen(tx).await {
Ok(()) => {}
Err(e) if e.to_string().contains("requires a `secret`") => {
// Pure config defect: stop, generate a secret, update config, restart.
// Never catch-and-continue — that would run an unauthenticated endpoint.
}
Err(e) => return Err(e),
} Prevention
- Generate secrets with openssl rand -hex 32 and store them via the secret store, not inline in committed configs.
- Add a config-lint step in CI that fails when [channels.webhook.*] lacks secret.
- Teach senders to produce x-webhook-signature: sha256=<hex HMAC-SHA256 of the raw body> from day one.
- When a config template omits the key intentionally, comment it loudly so merges never drop the real secret.
When it happens
Trigger: Config declares [channels.webhook.<alias>] with send_url/listen_path but omits secret; secret key set to an empty string; a config generated from an example template that predates the requirement. listen() (channel startup) fails before the axum router binds, so the whole daemon reports the channel as failed.
Common situations: First-time setup following an older example config; splitting config into a template and per-env overlay where the overlay held the secret but was not merged; migrating a dev setup (which used to work secretless) to a newer release that made the secret mandatory.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Edge TTS binary_path must be a bare command name without pat
- Refusing to transmit sensitive data over non-HTTPS URL: URL
- security.nevis: {msg}
- Serial path not allowed: {}. Allowed: {}
- runtime.wasm.tools_dir must not contain '..' path traversal
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/6832586accab8954.
Report an issue: GitHub.