Add an opt-in lead (sends their first message)
const url = 'https://app.whatsetter.com/api/v1/campaigns/example/leads';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"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"}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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:
- Phone validation and WhatsApp-registration check, deduplication against leads already contacted.
- Daily first-contact quota and anti-bot pacing: over-quota leads are queued for the next window, never dropped.
- 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…). - 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=....
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Campaign document id, or its campaign_id slug.
Request Bodyrequired
Section titled “Request Bodyrequired”object
International format, e.g. +33612345678 (6 to 15 digits, +, spaces and dashes tolerated).
Where the lead came from (defaults to api).
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.
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" }}Responses
Section titled “Responses”Lead accepted into the first-contact pipeline
object
object
Echo of what you sent, or null.
Example
{ "data": { "status": "accepted", "phone": "+33612345678" }}Missing, unknown or revoked API key
object
object
Examplegenerated
{ "error": { "code": "example", "message": "example" }}The key lacks the required scope
object
object
Examplegenerated
{ "error": { "code": "example", "message": "example" }}Resource not found in this workspace
object
object
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).
object
object
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)
object
object
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.
object
object
Example
{ "error": { "code": "upstream_error", "message": "The first-message pipeline is unreachable. Try again in a moment." }}
