zeroclaw-labs/zeroclaw · error
WhatsApp API error: {status}
Error message
WhatsApp API error: {status} What it means
`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.
Source
Thrown at crates/zeroclaw-channels/src/whatsapp.rs:689
}
});
self.post_to_meta(&url, &body).await
}
async fn post_to_meta(&self, url: &str, body: &serde_json::Value) -> anyhow::Result<()> {
let resp = self
.http_client()
.post(url)
.bearer_auth(&self.access_token)
.header("Content-Type", "application/json")
.json(body)
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
let error_body = resp.text().await.unwrap_or_default();
::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:");
anyhow::bail!("WhatsApp API error: {status}");
}
Ok(())
}
}
/// One section in an interactive list message. Sections group related
/// rows under a header.
#[derive(Debug, Clone)]
pub struct InteractiveListSection {
/// Section header (Meta caps at 24 chars; we truncate).
pub title: String,
/// Rows in this section. Up to 10 per Meta's limit.
pub rows: Vec<InteractiveListRow>,
}
/// One row in an interactive list message.
#[derive(Debug, Clone)]
pub struct InteractiveListRow {View on GitHub (pinned to 88bb9c8533)
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.
Example fix
// before
channel.send_interactive_buttons(to, "Proceed?", &buttons).await?;
// after: retry transient statuses, surface contract/auth errors
let mut backoff = std::time::Duration::from_secs(2);
for _ in 0..3 {
match channel.send_interactive_buttons(to, "Proceed?", &buttons).await {
Ok(()) => break,
Err(e) => {
let msg = e.to_string();
if msg.contains("429") || msg.contains("WhatsApp API error: 5") {
tokio::time::sleep(backoff).await;
backoff *= 2;
} else {
return Err(e); // 400/401: fix payload or token, do not retry
}
}
}
} Defensive patterns
Strategy: retry
Validate before calling
// Preflight credentials before an interactive burst
if !channel.health_check().await {
anyhow::bail!("WhatsApp Cloud API preflight failed: check access token / phone-number id");
} Try / catch
match channel.send_interactive_buttons(to, body, &buttons).await {
Ok(()) => Ok(()),
Err(e) => {
let msg = e.to_string();
// real cause (Meta error body) is in the zeroclaw log's error_body attr
if msg.contains("WhatsApp API error: 429") || msg.contains("WhatsApp API error: 5") {
// transient: back off and retry
retry_with_backoff(|| channel.send_interactive_buttons(to, body, &buttons)).await
} else {
Err(e) // 4xx contract/auth failure: fix payload or token
}
}
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- WhatsApp interactive buttons require 1..=3 options (got {});
- GET /users/me/channels returned {}
- login failed ({status}): {body}
- Mattermost WebSocket authentication was rejected
- Mochat send message failed ({status}): {err}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2df68aa822b3e99c.
Report an issue: GitHub.