Skip to content

Send a WhatsApp message to a lead

POST
/messages
curl --request POST \
--url https://app.whatsetter.com/api/v1/messages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "lead_id": "example", "text": "example", "humanize": true }'

Sends a text message to a lead through WhatSetter’s anti-ban engine. Two rules are enforced server-side and cannot be bypassed:

  1. No cold outreach: the lead must already have a conversation. First contact goes through POST /lists/{id}/leads + a campaign (paced, verified sending). Otherwise you get 409 lead_not_contacted.
  2. Daily quota: the send consumes the same daily budget as the campaign engine for that WhatsApp number. When the number’s safe daily volume is reached you get 429 quota_exceeded (reset at midnight UTC). Check the X-Quota-Remaining response header.

The message is sent from the lead’s own campaign number and appears in the dashboard conversation thread. Requires scope messages:send.

Note: 201 means the message was accepted by the WhatsApp engine. Like all WhatsApp APIs, final delivery is asynchronous (device online, contact state…). Delivery/read receipts will surface via webhooks in a future version.

Media typeapplication/json
object
lead_id
required

The lead’s id from /leads or /conversations.

string
text
required
string
<= 4096 characters
humanize

When true (default, recommended), the send replays a human choreography (read receipt, “typing…” indicator, a typing delay paced to the message length) before the message goes out. Expect the request to take ~3–15 s. Set to false for an instant send (still quota-gated).

boolean
default: true

Message sent

Media typeapplication/json
object
data
object
id

Conversation row id

string | null
message_id
string
lead_id
string
text
string
sent_at
string format: date-time
quota
object
limit
integer
used
integer
remaining
integer
resets_at
string format: date-time
Examplegenerated
{
"data": {
"id": "example",
"message_id": "example",
"lead_id": "example",
"text": "example",
"sent_at": "2026-04-15T12:00:00Z",
"quota": {
"limit": 1,
"used": 1,
"remaining": 1,
"resets_at": "2026-04-15T12:00:00Z"
}
}
}
X-Quota-Limit
integer

Daily quota of the sending number

X-Quota-Remaining
integer
X-Quota-Reset
string format: date-time

Missing, unknown or revoked API key

Media typeapplication/json
object
error
object
code
string
message
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example"
}
}

The key lacks the required scope

Media typeapplication/json
object
error
object
code
string
message
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example"
}
}

Resource not found in this workspace

Media typeapplication/json
object
error
object
code
string
message
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example"
}
}

Either the lead was never contacted (lead_not_contacted: cold outreach must go through lists + campaigns) or a request with the same Idempotency-Key is still in flight (request_in_flight).

Media typeapplication/json
object
error
object
code
string
message
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example"
}
}

Invalid parameter or body

Media typeapplication/json
object
error
object
code
string
message
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example"
}
}

Daily anti-ban quota reached for this number

Media typeapplication/json
object
error
object
code
string
message
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example"
}
}

Send failed (transient), retry shortly

Media typeapplication/json
object
error
object
code
string
message
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example"
}
}

The campaign’s WhatsApp number is disconnected

Media typeapplication/json
object
error
object
code
string
message
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example"
}
}