Rust reqwest returning 400 when pushing proactive webchat notification to existing session

The Rust service in Sao Paulo subscribes to the Genesys Cloud WebSocket stream for conversation events. Business rules trigger a proactive notification to a guest who already has an active web messaging session. The plan was straightforward. Pull the conversation ID from the stream, construct a JSON payload, and POST it to /api/v2/conversations/messages/{conversationId}.

The endpoint throws a 400 Bad Request every single time. The response just returns "message": "Invalid message payload". We don’t get any field validation details. We’ve swapped the to address format three times. The guest identifier matches exactly what the conversations.message.created event broadcasts. The bearer token works fine on other calls.

let payload = serde_json::json!({
 "to": format!("webchat:{}", guest_address),
 "from": format!("webchat:{}", agent_address),
 "text": "Your case status changed.",
 "channel": "webChat",
 "metadata": {
 "source": "proactive-notification"
 }
});

let resp = reqwest::Client::new()
 .post(&format!("{base_url}/api/v2/conversations/messages/{}", conv_id))
 .header("Authorization", format!("Bearer {}", token))
 .header("Content-Type", "application/json")
 .json(&payload)
 .send()
 .await?;

The Guest API docs hint at a different payload structure for proactive pushes. Maybe the from field actually needs an internal agent ID instead of a webchat URI. Or perhaps the proactive flow routes through /api/v2/conversations/webchat/messages instead. The WebSocket stream shows the session as active, but the REST call keeps rejecting the body. Tokio handles the async dispatch without issues. The problem sits entirely in the payload shape or endpoint routing. We keep guessing at the channel object structure. The validation rules aren’t documented clearly enough.

Anyone else hit this wall with proactive webchat messages? The error response lacks the exact field that’s breaking the validation. Just need the right JSON shape.