{"record":{"id":"2df68aa822b3e99c","repo":"zeroclaw-labs/zeroclaw","slug":"whatsapp-api-error-status","errorCode":null,"errorMessage":"WhatsApp API error: {status}","messagePattern":"WhatsApp API error: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/whatsapp.rs","lineNumber":689,"sourceCode":"            }\n        });\n        self.post_to_meta(&url, &body).await\n    }\n\n    async fn post_to_meta(&self, url: &str, body: &serde_json::Value) -> anyhow::Result<()> {\n        let resp = self\n            .http_client()\n            .post(url)\n            .bearer_auth(&self.access_token)\n            .header(\"Content-Type\", \"application/json\")\n            .json(body)\n            .send()\n            .await?;\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let error_body = resp.text().await.unwrap_or_default();\n            ::zeroclaw_log::record!(ERROR, ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail).with_outcome(::zeroclaw_log::EventOutcome::Failure).with_attrs(::serde_json::json!({\"status\": status.to_string(), \"error_body\": error_body})), \"WhatsApp interactive send failed:\");\n            anyhow::bail!(\"WhatsApp API error: {status}\");\n        }\n        Ok(())\n    }\n}\n\n/// One section in an interactive list message. Sections group related\n/// rows under a header.\n#[derive(Debug, Clone)]\npub struct InteractiveListSection {\n    /// Section header (Meta caps at 24 chars; we truncate).\n    pub title: String,\n    /// Rows in this section. Up to 10 per Meta's limit.\n    pub rows: Vec<InteractiveListRow>,\n}\n\n/// One row in an interactive list message.\n#[derive(Debug, Clone)]\npub struct InteractiveListRow {","sourceCodeStart":671,"sourceCodeEnd":707,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/whatsapp.rs#L671-L707","documentation":"`post_to_meta` posts interactive (button/list) messages to the Meta Graph API and bails whenever the response status is not 2xx. Only the HTTP status code reaches the returned error; Meta's detailed error body is recorded to the zeroclaw log on the same event (attrs `status` and `error_body`, module whatsapp), so the log is where the real cause lives. Common statuses: 401 bad token, 400 payload contract violation, 429 rate limit, 5xx Meta incidents.","triggerScenarios":"Any non-success Graph API response during `send_interactive_buttons` or `send_interactive_list`: expired or invalid access token (401), payload that violates Meta's limits such as over-long button titles or invalid characters (400), messaging rate limit after bulk sends (429), or a Meta-side incident (5xx).","commonSituations":"Temporary WhatsApp access tokens expiring after 24h instead of using a permanent system-user token; button/row titles that break Meta's length or character rules; burst sends tripping rate limits; Graph API version deprecation.","solutions":["Inspect the zeroclaw log entry for this send — the `error_body` attr contains Meta's error code and message, which names the exact field or limit at fault.","On 401: refresh the WhatsApp access token; prefer a permanent token from a system user in Meta Business Manager.","On 429: slow down sends and add exponential backoff between batches.","On 5xx: retry with backoff and check Meta's platform status page."],"exampleFix":"// before\nchannel.send_interactive_buttons(to, \"Proceed?\", &buttons).await?;\n\n// after: retry transient statuses, surface contract/auth errors\nlet mut backoff = std::time::Duration::from_secs(2);\nfor _ in 0..3 {\n    match channel.send_interactive_buttons(to, \"Proceed?\", &buttons).await {\n        Ok(()) => break,\n        Err(e) => {\n            let msg = e.to_string();\n            if msg.contains(\"429\") || msg.contains(\"WhatsApp API error: 5\") {\n                tokio::time::sleep(backoff).await;\n                backoff *= 2;\n            } else {\n                return Err(e); // 400/401: fix payload or token, do not retry\n            }\n        }\n    }\n}","handlingStrategy":"retry","validationCode":"// Preflight credentials before an interactive burst\nif !channel.health_check().await {\n    anyhow::bail!(\"WhatsApp Cloud API preflight failed: check access token / phone-number id\");\n}","typeGuard":null,"tryCatchPattern":"match channel.send_interactive_buttons(to, body, &buttons).await {\n    Ok(()) => Ok(()),\n    Err(e) => {\n        let msg = e.to_string();\n        // real cause (Meta error body) is in the zeroclaw log's error_body attr\n        if msg.contains(\"WhatsApp API error: 429\") || msg.contains(\"WhatsApp API error: 5\") {\n            // transient: back off and retry\n            retry_with_backoff(|| channel.send_interactive_buttons(to, body, &buttons)).await\n        } else {\n            Err(e) // 4xx contract/auth failure: fix payload or token\n        }\n    }\n}","preventionTips":["Use a permanent system-user access token instead of 24h temporary tokens.","Keep button/row titles within Meta's length limits and character rules.","Correlate every occurrence with the zeroclaw log entry carrying `error_body` before changing code."],"tags":["whatsapp","cloud-api","http","meta-graph-api","auth","rust"],"backgroundTag":"api-error-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}