Skip to content

Messages

POST /messages writes to a lead from the campaign’s own WhatsApp number, as if a teammate had taken over in the dashboard. The anti-ban rules are enforced server-side and cannot be bypassed.

Method Path Scope
POST /messages messages:send
Terminal window
curl -X POST "https://app.whatsetter.com/api/v1/messages" \
-H "Authorization: Bearer ws_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8c3d2f9e-7b1a-4e6f-9c0d-2a1b3c4d5e6f" \
-d '{"lead_id":"66f1a2b3c4d5e6f7a8b9c0d1","text":"Are we still on for tomorrow at 2pm?"}'
{
"data": {
"id": "66f1a2b3c4d5e6f7a8b9c0e5",
"message_id": "true_33612345678@c.us_3EB0C8F2A1D4B5E6F7A8",
"lead_id": "66f1a2b3c4d5e6f7a8b9c0d1",
"text": "Are we still on for tomorrow at 2pm?",
"sent_at": "2026-09-19T08:12:04.000Z",
"humanized": true,
"typing_ms": 3210,
"quota": {
"limit": 250,
"used": 41,
"remaining": 209,
"resets_at": "2026-09-20T00:00:00.000Z"
}
}
}
Field Meaning
id The conversation row id in WhatSetter, or null if the row could not be written after the send.
message_id The WhatsApp message id.
lead_id The lead you wrote to.
text The text sent.
sent_at When the engine accepted it.
humanized Whether the human choreography was played.
typing_ms The typing delay that was simulated, 0 when humanize is false.
quota The number’s daily budget after this send.

201 means the message was accepted by the WhatsApp engine. Final delivery is asynchronous, like any WhatsApp API. The message appears in the dashboard conversation as a manual reply.

The lead must already have a conversation: at least one message exchanged. Otherwise you get 409 lead_not_contacted. First contact goes through a list and campaign or an opt-in push.

Each send consumes the same daily budget as the campaign engine for that WhatsApp number (250 messages a day when no specific quota is set). When it is reached: 429 quota_exceeded, reset at midnight UTC.

Every 201 and 429 carries the budget in headers:

X-Quota-Limit: 250
X-Quota-Remaining: 209
X-Quota-Reset: 2026-09-20T00:00:00.000Z

If the campaign has no WhatsApp number, or the number is disconnected, you get 503 agent_disconnected. Check whatsapp_connected on GET /campaigns and reconnect it from the dashboard.

By default (humanize: true) the send replays a human choreography: read receipt, typing indicator, a typing delay paced to the message length, then the send. Expect the request to take 3 to 15 seconds. This is the recommended mode.

"humanize": false sends instantly, still quota-gated. Use it only for time-critical confirmations.

text is required, 1 to 4096 characters, text only.

A message API must be safe to retry: a timeout must never turn into a second real WhatsApp message. Pass an Idempotency-Key header on every send, unique per intent (a UUID, an order id…), up to 200 characters. WhatSetter remembers it for 15 minutes.

Situation What a retry with the same key gets
The first call answered 201 The same 201 body, with the header X-Idempotent-Replay: true. No second message.
The first call is still running 409 request_in_flight. Wait and retry.
The first call failed cleanly (422, 409, 429, 503) The key is released: your retry is a real new attempt.
The first call timed out (502 send_failed) 409 request_in_flight for the rest of the window: the message may have gone out. Check the conversation transcript before sending again.
HTTP code When
401 missing_api_key, invalid_api_key Key absent, unknown or revoked.
403 insufficient_scope The key lacks messages:send.
404 not_found Unknown lead, or a lead of another workspace.
409 lead_not_contacted The lead has never been contacted. Go through a list or an opt-in push.
409 request_in_flight Same Idempotency-Key still processing, or an earlier attempt with it timed out.
422 validation_error Missing lead_id, empty or too long text, humanize not a boolean, key over 200 characters.
429 quota_exceeded Daily budget of the number reached. See X-Quota-Reset.
429 rate_limited Too many requests this minute. Honor Retry-After.
500 internal_error Messaging backend not configured on our side.
502 send_failed The engine refused or did not confirm the send. Retry with the same key.
503 agent_disconnected No number on the campaign, or the number is offline.