nautechsystems/nautilus_trader · error · anyhow::Error

Failed to decode API secret

Error message

Failed to decode API secret: {e}

What it means

Kraken API secrets are base64-encoded; sign_spot decodes the stored api_secret with STANDARD base64 before computing the HMAC signature. This error wraps the base64 decode failure when the configured API secret is not valid base64.

Solutions

  1. Paste the API secret exactly as provided by Kraken (it is base64 already) without decoding or modifying it
  2. Strip whitespace/newlines from the secret string before constructing credentials
  3. Verify you are not swapping the API key and secret fields
  4. Validate with: python -c "import base64; base64.b64decode('<secret>')"

Example fix

// before
let creds = KrakenCredentials::new(api_key, my_decoded_hex_secret);
// after
let secret = my_raw_kraken_secret.trim().to_string();
let creds = KrakenCredentials::new(api_key, secret);
Defensive patterns

Strategy: validation

Validate before calling

import base64
base64.b64decode(secret, validate=True)  # raises if not valid standard base64

Type guard

function isValidBase64(s) { return /^[A-Za-z0-9+/]+={0,2}$/.test(s) && Buffer.from(s, 'base64').length > 0; }

Try / catch

match creds.sign_spot(path, nonce, &params) {
    Ok(sig) => sig,
    Err(e) if e.to_string().contains("decode API secret") => {
        eprintln!("api_secret is not valid base64; re-copy from Kraken: {e}");
        return Err(SignError::BadSecret);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Creating/using KrakenCredentials with an api_secret that is the raw (already-decoded) hex/plain secret, contains whitespace, uses a URL-safe alphabet, or is otherwise invalid standard base64.

Common situations: Copying the API secret from Kraken and accidentally decoding it first; trailing newline/space from a copy-paste or env var; misconfiguring secret vs key fields; reading the secret from a file with encoding issues.

Understand the failure class

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/fe2233a4c20524c6. Report an issue: GitHub.

Appendix: source

Thrown at crates/adapters/kraken/src/common/credential.rs:174

    }

    /// Sign a request for Kraken Spot REST API.
    ///
    /// Kraken Spot uses HMAC-SHA512 with the following message:
    /// - path + SHA256(nonce + POST data)
    /// - The secret is base64 decoded before signing
    ///
    /// Note: "nonce + POST data" means the nonce value string is prepended
    /// to the URL-encoded POST body, e.g., "1234567890nonce=1234567890&param=value".
    pub fn sign_spot(
        &self,
        path: &str,
        nonce: u64,
        params: &HashMap<String, String>,
    ) -> anyhow::Result<(String, String)> {
        let secret = STANDARD
            .decode(&self.api_secret)
            .map_err(|e| anyhow::anyhow!("Failed to decode API secret: {e}"))?;

        let nonce_str = nonce.to_string();
        let mut post_data = format!("nonce={nonce_str}");

        if !params.is_empty() {
            let encoded = serde_urlencoded::to_string(params)
                .map_err(|e| anyhow::anyhow!("Failed to encode params: {e}"))?;
            post_data.push('&');
            post_data.push_str(&encoded);
        }

        let sha_input = format!("{nonce_str}{post_data}");
        let hash = digest::digest(&digest::SHA256, sha_input.as_bytes());
        let mut message = path.as_bytes().to_vec();
        message.extend_from_slice(hash.as_ref());
        let key = hmac::Key::new(hmac::HMAC_SHA512, &secret);
        let signature = hmac::sign(&key, &message);

View on GitHub (pinned to 18893faf8b)