zeroclaw-labs/zeroclaw · error · anyhow::Error
Nextcloud Talk: no bot secret configured (set bot_token or w
Error message
Nextcloud Talk: no bot secret configured (set bot_token or webhook_secret); refusing to send an unsigned request
What it means
Nextcloud Talk bots must sign every request with HMAC-SHA256 over `random + message` using the bot secret, sent as `X-Nextcloud-Talk-Bot-Random`/`-Signature` headers. `create_signed_request` refuses to build an unsigned request when `bot_token`/`webhook_secret` is unset, because the server would reject and throttle it — the code treats a missing secret as a hard configuration error.
Source
Thrown at crates/zeroclaw-channels/src/nextcloud_talk.rs:496
/// Generate a signed Nextcloud Talk bot API request. Fails closed
/// (returns `Err`, sends nothing) when no bot secret is configured —
/// a missing secret must never be papered over with a fabricated or
/// zero-key signature, since that produces an authenticated-looking
/// request that can trip Nextcloud's unauthenticated-request throttling.
///
/// Bots authenticate via HMAC-SHA256 over `random + message` (the bare
/// message text, matching the Nextcloud controller's verification,
/// which binds the `message` request parameter — not the raw JSON
/// request body), rendered as a bare hex digest (no `sha256=` prefix).
/// See: https://nextcloud-talk.readthedocs.io/en/latest/bots/
fn create_signed_request(
&self,
method: Method,
url: &str,
message: &str,
) -> anyhow::Result<reqwest::RequestBuilder> {
let Some(secret) = &self.bot_token else {
anyhow::bail!(
"Nextcloud Talk: no bot secret configured (set bot_token or webhook_secret); refusing to send an unsigned request"
);
};
let random = Uuid::new_v4().to_string();
let payload = format!("{random}{message}");
let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).map_err(|_| {
anyhow::Error::msg("Nextcloud Talk: failed to create HMAC from bot secret")
})?;
mac.update(payload.as_bytes());
let signature = hex::encode(mac.finalize().into_bytes());
Ok(self
.client
.request(method, url)
.header("X-Nextcloud-Talk-Bot-Random", random)
.header("X-Nextcloud-Talk-Bot-Signature", signature)
.header("OCS-APIRequest", "true")View on GitHub (pinned to 88bb9c8533)
Solutions
- Set `bot_token` (or `webhook_secret`) to the secret displayed when the bot was registered in Nextcloud Talk
- If the secret was lost, remove and re-add the bot in Talk settings to get a new one
- Re-check the channel config keys against the Nextcloud Talk channel docs
Example fix
# before [channels.nextcloud_talk.bot] base_url = "https://cloud.example.com" webhook_url = "https://cloud.example.com/..." # after [channels.nextcloud_talk.bot] base_url = "https://cloud.example.com" webhook_url = "https://cloud.example.com/..." bot_token = "<secret shown at bot creation>"
Defensive patterns
Strategy: validation
Validate before calling
if talk_config
.bot_token
.as_deref()
.is_none_or(|t| t.trim().is_empty())
{
return Err(anyhow::anyhow!(
"nextcloud_talk requires bot_token/webhook_secret; refusing unsigned sends"
));
} Prevention
- Fail config validation at startup when a nextcloud_talk channel has no bot_token/webhook_secret
- Store the bot secret alongside the webhook URL from day one — both are shown at bot creation
- Treat signing capability as a hard prerequisite, never optional, for Talk channels
When it happens
Trigger: Calling `send`/`post_to_room` (including draft/finalize flows) on a Nextcloud Talk channel configured without `bot_token` or `webhook_secret` — e.g. only a webhook URL was supplied.
Common situations: Setup copied the webhook URL but not the secret shown at bot creation; secret stored under a differently named config key; config edit dropped the token line.
Related errors
- matrix: configure either `access_token` or `password`
- createSession failed ({status}): {body}
- matrix login requires either access_token or user_id+passwor
- Talk API error: {status}
- model_provider `{family}` has multiple configured aliases; u
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/bfd1567cb56a198b.
Report an issue: GitHub.