Quickstart
Five requests, shown in cURL, JavaScript and Python. At the end you have a key, a list of contacts and a webhook that tells you when a lead is qualified.
Before you start
Section titled “Before you start”- You are owner or admin of your WhatSetter workspace. The API page says it: Only owners and admins can manage API keys.
- You have
curl, Node.js 18 or newer, or Python 3 withrequests. - The base URL is
https://app.whatsetter.com/api/v1.
1. Create an API key
Section titled “1. Create an API key”- Open Settings, then API & MCP.
- Click Create a key.
- Fill in Key name (for example
CRM sync) and choose the Permissions: Full access, or Custom to pick scopes one by one. - Click Create the key.
- Copy the key. The dialog reads Copy your key now. It will never be shown again. Then click I saved my key.
Store it in an environment variable, never in your code:
export WHATSETTER_API_KEY="ws_live_…"2. Check the key
Section titled “2. Check the key”GET /me needs no scope. It tells you which workspace and which permissions the key has.
curl https://app.whatsetter.com/api/v1/me \ -H "Authorization: Bearer $WHATSETTER_API_KEY"const res = await fetch('https://app.whatsetter.com/api/v1/me', { headers: { Authorization: `Bearer ${process.env.WHATSETTER_API_KEY}` },});console.log(await res.json());import os, requests
r = requests.get( "https://app.whatsetter.com/api/v1/me", headers={"Authorization": f"Bearer {os.environ['WHATSETTER_API_KEY']}"},)print(r.json()){ "data": { "team_id": "6b0c1d2e3f405162738a", "key": { "id": "66f1a2b3c4d5e6f7a8b9c0d1", "name": "CRM sync", "scopes": ["leads:read", "leads:write", "lists:write", "webhooks:manage"] } }}A 401 invalid_api_key means the key was mistyped or revoked. A 401 missing_api_key means the header did not reach us.
3. List your leads
Section titled “3. List your leads”GET /leads needs leads:read. Filter by status, campaign_id, phone, source or since, and page with limit (1 to 100) and cursor.
curl "https://app.whatsetter.com/api/v1/leads?status=qualified&limit=10" \ -H "Authorization: Bearer $WHATSETTER_API_KEY"const url = new URL('https://app.whatsetter.com/api/v1/leads');url.searchParams.set('status', 'qualified');url.searchParams.set('limit', '10');
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.WHATSETTER_API_KEY}` },});const { data, pagination } = await res.json();console.log(data.length, pagination.next_cursor);import os, requests
r = requests.get( "https://app.whatsetter.com/api/v1/leads", params={"status": "qualified", "limit": 10}, headers={"Authorization": f"Bearer {os.environ['WHATSETTER_API_KEY']}"},)body = r.json()print(len(body["data"]), body["pagination"]["next_cursor"]){ "data": [ { "id": "66f1a2b3c4d5e6f7a8b9c0d1", "phone": "33612345678", "name": "Julien", "status": "qualified", "qualified": true, "qualified_at": "2026-09-18T09:12:41.000Z", "campaign_id": "acme-france-1-ab12", "source": "list", "tags": ["hot"], "messages_count": 14, "last_message_at": "2026-09-18T09:12:00.000Z", "last_message_direction": "inbound" } ], "pagination": { "has_more": false, "next_cursor": null }}The lead object is shortened here. When has_more is true, send next_cursor back as cursor to get the next page.
4. Import a list of contacts
Section titled “4. Import a list of contacts”Two calls: POST /lists creates an empty list, then POST /lists/{id}/leads pushes up to 500 contacts per call. Both need lists:write. Phones are deduplicated inside the batch and against the list.
# 1. Create the listcurl -X POST https://app.whatsetter.com/api/v1/lists \ -H "Authorization: Bearer $WHATSETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"CRM sync, hot leads"}'
# 2. Push contacts into it (replace LIST_ID with the id from step 1)curl -X POST https://app.whatsetter.com/api/v1/lists/LIST_ID/leads \ -H "Authorization: Bearer $WHATSETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"leads":[{"phone":"+33612345678","name":"Julien","custom_variables":{"city":"Lyon"}}]}'const headers = { Authorization: `Bearer ${process.env.WHATSETTER_API_KEY}`, 'Content-Type': 'application/json',};
// 1. Create the listconst created = await fetch('https://app.whatsetter.com/api/v1/lists', { method: 'POST', headers, body: JSON.stringify({ name: 'CRM sync, hot leads' }),});const { data: list } = await created.json();
// 2. Push contacts into itconst imported = await fetch(`https://app.whatsetter.com/api/v1/lists/${list.id}/leads`, { method: 'POST', headers, body: JSON.stringify({ leads: [{ phone: '+33612345678', name: 'Julien', custom_variables: { city: 'Lyon' } }], }),});console.log(await imported.json());import os, requests
headers = {"Authorization": f"Bearer {os.environ['WHATSETTER_API_KEY']}"}base = "https://app.whatsetter.com/api/v1"
# 1. Create the listlst = requests.post(f"{base}/lists", headers=headers, json={"name": "CRM sync, hot leads"}).json()["data"]
# 2. Push contacts into itr = requests.post( f"{base}/lists/{lst['id']}/leads", headers=headers, json={"leads": [{"phone": "+33612345678", "name": "Julien", "custom_variables": {"city": "Lyon"}}]},)print(r.json()){ "data": { "imported": 1, "duplicates_in_list": 0, "rejected": [], "lead_ids": ["66f1a2b3c4d5e6f7a8b9c0d2"] }}5. Create a webhook
Section titled “5. Create a webhook”POST /webhooks needs webhooks:manage. The endpoint must be HTTPS.
curl -X POST https://app.whatsetter.com/api/v1/webhooks \ -H "Authorization: Bearer $WHATSETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://your-server.com/webhooks/whatsetter","events":["contact.qualified","contact.not_qualified"]}'const res = await fetch('https://app.whatsetter.com/api/v1/webhooks', { method: 'POST', headers: { Authorization: `Bearer ${process.env.WHATSETTER_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://your-server.com/webhooks/whatsetter', events: ['contact.qualified', 'contact.not_qualified'], }),});const { data } = await res.json();console.log(data.secret); // whsec_… shown once, store it nowimport os, requests
r = requests.post( "https://app.whatsetter.com/api/v1/webhooks", headers={"Authorization": f"Bearer {os.environ['WHATSETTER_API_KEY']}"}, json={"url": "https://your-server.com/webhooks/whatsetter", "events": ["contact.qualified", "contact.not_qualified"]},)print(r.json()["data"]["secret"]) # whsec_… shown once, store it now{ "data": { "id": "66f1c0ffee00000000000abc", "url": "https://your-server.com/webhooks/whatsetter", "events": ["contact.qualified", "contact.not_qualified"], "active": true, "description": null, "secret_prefix": "whsec_3f9a", "secret": "whsec_3f9a…", "last_delivery_at": null, "last_delivery_status": null, "created_at": "2026-09-19T10:00:00.000Z" }}The secret is shown only in this response. Store it: you need it to verify the signature of every delivery. Then send yourself a signed test event with POST /webhooks/{id}/test.
Next steps
Section titled “Next steps”- Authentication and permissions: scopes, rate limits, key rotation.
- Errors, limits and idempotency: every
code, and how to retry safely. - Guides by resource: leads, campaigns, messages, groups, blocked contacts, links.
- MCP server: give the same key to Claude, Cursor or ChatGPT.
- Use these docs with AI assistants: let an assistant build the integration for you.

