Documentation topics
Developer docs

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!"}'
Illustrative request · excerptSee reply API →

Fields

Send JSON with Content-Type: application/json.

FieldTypeRules
tostring, requiredRecipient 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.
textstring, required1–4,096 characters, not only whitespace.
reply_tostring, optionalMessage ID (wamid) to quote, from a received message.
preview_urlboolean, optionalDefault 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.