Send a text message
POST https://api.unpoll.cc/v1/agents/{id}/messages
Authenticate with the agent's API key. Copy data.from from a received message.received event into to, unchanged. WhatsApp lets an agent message only the person who created it; other recipients are rejected with 403 recipient_not_allowed.
Request examples
These are illustrative HTTP-client excerpts, not SDKs or complete runnable programs. In the examples, base or BASE_URL is https://api.unpoll.cc; url is https://api.unpoll.cc/v1/agents/{id}/messages with your Unpoll agent UUID in place of {id}. sender is the received message's data.from, and api_key, apiKey, or UNPOLL_API_KEY is that agent's server-side key. Configure timeouts, check the HTTP status and error code, and handle failures explicitly in your application.
Rust
let sent = client
.post(format!("{base}/v1/agents/{agent_id}/messages"))
.bearer_auth(api_key)
.json(&json!({ "to": sender, "text": "Hello!" }))
.send().await?
.json::<Value>().await?;
Python
sent = requests.post(
f"{base}/v1/agents/{agent_id}/messages",
headers={"Authorization": f"Bearer {api_key}"},
json={"to": sender, "text": "Hello!"},
).json()
Go
payload, _ := json.Marshal(map[string]string{"to": sender, "text": "Hello!"})
req, _ := http.NewRequest("POST", url, bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
response, _ := client.Do(req)
Node.js
const response = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json" },
body: JSON.stringify({ to: sender, text: "Hello!" }),
});
const { message_id } = await response.json();
cURL
curl "$BASE_URL/v1/agents/$AGENT_ID/messages" \
-H "Authorization: Bearer $UNPOLL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"user:50972923564215","text":"Hello!"}'
Fields
Send JSON with Content-Type: application/json.
| Field | Type | Rules |
|---|---|---|
to | string, required | Recipient as user:<id>, copied from the from field of a received message. At most 128 visible ASCII characters. Send it back unchanged; the recipient must be the agent's creator. |
text | string, required | 1–4,096 characters, not only whitespace. |
reply_to | string, optional | Message ID (wamid) to quote, from a received message. |
preview_url | boolean, optional | Default false. Show a link preview for the first URL in text. |
Unknown fields are rejected with 400 invalid_request. Request bodies are limited to 64 KiB. Only text is supported: Unpoll does not send media or templates.
Response
A 200 response means WhatsApp accepted the message, not that it was delivered or read:
{
"message_id": "wamid.HBgONTA5...",
"to": "user:50972923564215"
}
Keep message_id. Later message.status webhooks carry it as their data.id, with delivered or read status.
Do not blindly retry
Unpoll never automatically retries a send. On 502 send_outcome_unknown, a provider failure, timeout, or broken response means the message may already have been sent. Wait for a corresponding status webhook before sending again, or explicitly accept that a retry may duplicate the message. A timeout at your own HTTP client can also leave the outcome unknown.
See the complete error table for whether a send may have gone out, and rate limits and ordering before issuing concurrent requests.