Skip to content

Add an opt-in lead (sends their first message)

POST
/campaigns/{id}/leads
curl --request POST \
--url https://app.whatsetter.com/api/v1/campaigns/example/leads \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "phone": "+33612345678", "name": "Marie Dupont", "email": "marie@acme.fr", "source_url": "crm:hubspot", "external_reference": "4471", "additional_data": { "offre": "Pack Pro", "ville": "Lyon", "budget": "500-1000" } }'

The CRM primitive for opt-in leads: when someone signs up on your client’s funnel or CRM, push them here and WhatSetter runs the exact same pipeline as the campaign’s public sales-page webhook, with the anti-ban protections applied automatically:

  1. Phone validation and WhatsApp-registration check, deduplication against leads already contacted.
  2. Daily first-contact quota and anti-bot pacing: over-quota leads are queued for the next window, never dropped.
  3. The AI writes a personalized first message from the campaign’s template, the lead’s name, the source, and every key you pass in additional_data (budget, city, product of interest, plan…).
  4. The lead lands in the campaign and the agent handles replies.

The pipeline is asynchronous, so the endpoint answers 202 as soon as the lead is accepted. Follow progress with GET /leads (filter on campaign_id) or the signed webhooks. Accepts the campaign document id or its campaign_id slug in the path. Requires scope messages:send.

Correlating with your own records

Because the lead row is created later (after deduplication and quota/pacing checks), the 202 cannot return a WhatSetter lead id — there is none yet. Send your own id in external_reference instead. We store it verbatim and echo it back as contact.external_reference on every outbound webhook, so you can match our events to your records with no phone-number join. You can also look it up any time with GET /leads?external_reference=....

id
required
string

Campaign document id, or its campaign_id slug.

Media typeapplication/json
object
phone
required

International format, e.g. +33612345678 (6 to 15 digits, +, spaces and dashes tolerated).

string
<= 32 characters
name
string
<= 255 characters
email
string
<= 255 characters
source_url

Where the lead came from (defaults to api).

string
<= 255 characters
external_reference

Your own id for this contact (CRM record id, row id…). Stored verbatim, never parsed, and echoed back as contact.external_reference on every webhook. This is the supported way to correlate our events with your records.

string
<= 255 characters
additional_data

Free-form CRM fields (up to 30 keys, 2000 characters total). The AI uses them to personalize the first message. These are not stored and not returned — use external_reference for anything you need back. Keys that belong to the funnel itself are ignored inside additional_data: phone, phone_raw, phone_formatted, phone_whatsapp, name, email, source_url, external_reference, team_slug, campaign_slug, state, headers, params, query.

object
Example
{
"phone": "+33612345678",
"name": "Marie Dupont",
"email": "marie@acme.fr",
"source_url": "crm:hubspot",
"external_reference": "4471",
"additional_data": {
"offre": "Pack Pro",
"ville": "Lyon",
"budget": "500-1000"
}
}

Lead accepted into the first-contact pipeline

Media typeapplication/json
object
data
object
status
string
campaign_id
string
phone
string
external_reference

Echo of what you sent, or null.

string
nullable
note
string
Example
{
"data": {
"status": "accepted",
"phone": "+33612345678"
}
}

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"
}
}

invalid_state: the campaign is not active (pause / draft / archived), or it has never been opened in the dashboard and has no funnel identifiers yet (open it once, or contact support).

Media typeapplication/json
object
error
object
code
string
message
string
Example
{
"error": {
"code": "invalid_state",
"message": "Leads can only be pushed to an active campaign (current status: paused)."
}
}

Invalid parameter or body (also an invalid or expired cursor)

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

upstream_error: the first-message pipeline is unreachable or did not accept the lead. Safe to retry in a moment.

Media typeapplication/json
object
error
object
code
string
message
string
Example
{
"error": {
"code": "upstream_error",
"message": "The first-message pipeline is unreachable. Try again in a moment."
}
}