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

  1. Set `bot_token` (or `webhook_secret`) to the secret displayed when the bot was registered in Nextcloud Talk
  2. If the secret was lost, remove and re-add the bot in Talk settings to get a new one
  3. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/bfd1567cb56a198b. Report an issue: GitHub.