Running campaigns
Send templated messages to a named contact list with per-contact variables, and track progress end to end.
A campaign sends one message template to every contact in an
audience, with {variable} placeholders swapped in per contact.
This guide assumes you have an API key (Authentication) and an SDK client set up (Quickstart). Full endpoint details live in the Campaigns and Audiences reference.
1. Create an audience
aud = bt.audiences.create(
name="Summer 2026 launch",
contacts=[
{"to": "+972501234567", "variables": {"first_name": "Rachel"}},
{"to": "+15551234567", "variables": {"first_name": "Alex"}},
],
)
print(aud.id, aud.contact_count)const aud = await bt.audiences.create({
name: 'Summer 2026 launch',
contacts: [
{ to: '+972501234567', variables: { first_name: 'Rachel' } },
{ to: '+15551234567', variables: { first_name: 'Alex' } },
],
});$aud = $bt->audiences->create(
name: 'Summer 2026 launch',
contacts: [
['to' => '+972501234567', 'variables' => ['first_name' => 'Rachel']],
['to' => '+15551234567', 'variables' => ['first_name' => 'Alex']],
],
);aud = client.audiences.create(
name: "Summer 2026 launch",
contacts: [
{ "to" => "+972501234567", "variables" => { "first_name" => "Rachel" } },
{ "to" => "+15551234567", "variables" => { "first_name" => "Alex" } },
],
)
puts aud.id, aud.contact_countaud, _ := c.Audiences.Create(context.Background(), blueticks.CreateAudienceParams{
Name: "Summer 2026 launch",
Contacts: []blueticks.AudienceContactInput{
{To: "+972501234567", Variables: map[string]string{"first_name": "Rachel"}},
{To: "+15551234567", Variables: map[string]string{"first_name": "Alex"}},
},
})curl -X POST https://api.blueticks.co/v1/audiences \
-H "Authorization: Bearer BLUETICKS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer 2026 launch",
"contacts": [
{"to":"+972501234567","variables":{"first_name":"Rachel"}},
{"to":"+15551234567","variables":{"first_name":"Alex"}}
]
}'Each contact's to accepts a phone number in international format OR a WhatsApp JID (@g.us for groups,
@newsletter for channels) — same rules as direct POST /v1/scheduled-messages.
Append more contacts to an existing audience later via
POST /v1/audiences/{id}/contacts — up to 1000 per request, duplicates
silently skipped:
curl -X POST 'https://api.blueticks.co/v1/audiences/string/contacts' \ -H 'Authorization: Bearer BLUETICKS_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "contacts": [ { "to": "string" } ]}'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.audiences.append_contacts( "id_01H7...", # request body fields…,)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.audiences.appendContacts('id_01H7...', { /* … */ });use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->audiences->appendContacts('id_01H7...', /* opts */);require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.audiences.append_contacts( "id_01H7...", # request body fields…,)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Audiences.AppendContacts(context.Background(), "id_01H7...", params){ "contacts": [ { "to": "string" } ]}List, fetch, rename, or delete an audience:
curl -X GET 'https://api.blueticks.co/v1/audiences' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.audiences.list()import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.audiences.list();use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->audiences->list();require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.audiences.list()import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Audiences.List(context.Background())curl -X GET 'https://api.blueticks.co/v1/audiences/string' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.audiences.get( "id_01H7...",)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.audiences.get('id_01H7...');use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->audiences->retrieve('id_01H7...');require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.audiences.get( "id_01H7...",)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Audiences.Get(context.Background(), "id_01H7...")curl -X PATCH 'https://api.blueticks.co/v1/audiences/string' \ -H 'Authorization: Bearer BLUETICKS_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "name": "string"}'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.audiences.update( "id_01H7...", # request body fields…,)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.audiences.update('id_01H7...', { /* … */ });use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->audiences->update('id_01H7...', /* opts */);require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.audiences.update( "id_01H7...", # request body fields…,)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Audiences.Update(context.Background(), "id_01H7...", params){ "name": "string"}curl -X DELETE 'https://api.blueticks.co/v1/audiences/string' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.audiences.delete( "id_01H7...",)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.audiences.delete('id_01H7...');use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->audiences->delete('id_01H7...');require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.audiences.delete( "id_01H7...",)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Audiences.Delete(context.Background(), "id_01H7...")Update or remove an individual contact's variables within an audience:
curl -X PATCH 'https://api.blueticks.co/v1/audiences/string/contacts/string' \ -H 'Authorization: Bearer BLUETICKS_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "to": "string", "variables": {}}'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.audiences.update_contact( "id_01H7...", "contactId_01H7...", # request body fields…,)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.audiences.updateContact('id_01H7...', 'contactId_01H7...', { /* … */ });use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->audiences->updateContact('id_01H7...', 'contactId_01H7...', /* opts */);require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.audiences.update_contact( "id_01H7...", "contactId_01H7...", # request body fields…,)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Audiences.UpdateContact(context.Background(), "id_01H7...", "contactId_01H7...", params){ "to": "string", "variables": {}}curl -X DELETE 'https://api.blueticks.co/v1/audiences/string/contacts/string' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.audiences.delete_contact( "id_01H7...", "contactId_01H7...",)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.audiences.deleteContact('id_01H7...', 'contactId_01H7...');use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->audiences->deleteContact('id_01H7...', 'contactId_01H7...');require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.audiences.delete_contact( "id_01H7...", "contactId_01H7...",)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Audiences.DeleteContact(context.Background(), "id_01H7...", "contactId_01H7...")Your workspace-wide contact book (everyone you've ever messaged) is at
GET /v1/contacts:
curl -X GET 'https://api.blueticks.co/v1/contacts' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.contacts.list()import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.contacts.list();use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->contacts->list();require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.contacts.list()import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Contacts.List(context.Background())2. Launch a campaign
Use {var_name} in text; each contact's variables fill in. Token
lookup is case-insensitive, and four built-ins are always available
without setting any variables: {whatsappname} (WhatsApp profile name),
{displayname}, {phone}, and {mobile}. If any contact is missing a
referenced variable, on_missing_variable
decides: "fail" (default — 400 request rejected before the campaign starts,
no messages sent) or "skip" (the campaign is accepted and contacts missing
the variable still receive the message with the literal {token} left in
place).
Two more optional fields: media_url (plus media_caption) attaches an
image/video/document to every message, and from (a phone number in international format)
picks the sending number when the workspace default isn't what you want.
cmp = bt.campaigns.create(
name="Launch blast",
audience_id="aud_01h7...",
text="Hey {first_name}! Your order ships today.",
on_missing_variable="fail",
)
print(cmp.id, cmp.status)const cmp = await bt.campaigns.create({
name: 'Launch blast',
audience_id: 'aud_01h7...',
text: 'Hey {first_name}! Your order ships today.',
on_missing_variable: 'fail',
});$cmp = $bt->campaigns->create(
'Launch blast',
'aud_01h7...',
['text' => 'Hey {first_name}! Your order ships today.', 'on_missing_variable' => 'fail'],
);cmp = client.campaigns.create(
name: "Launch blast",
audience_id: "aud_01h7...",
text: "Hey {first_name}! Your order ships today.",
on_missing_variable: "fail",
)
puts cmp.id, cmp.statuscmp, _ := c.Campaigns.Create(context.Background(), blueticks.CreateCampaignParams{
Name: "Launch blast",
AudienceID: "aud_01h7...",
Text: "Hey {first_name}! Your order ships today.",
OnMissingVariable: "fail",
})curl -X POST https://api.blueticks.co/v1/campaigns \
-H "Authorization: Bearer BLUETICKS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Launch blast",
"audience_id": "aud_01h7...",
"text": "Hey {first_name}! Your order ships today.",
"on_missing_variable": "fail"
}'Response includes live counters (sent_count, delivered_count,
read_count, failed_count) plus status: "pending".
3. Track progress
cmp = bt.campaigns.get("cmp_01h7...")
print(cmp.status, cmp.sent_count, cmp.delivered_count)const cmp = await bt.campaigns.get('cmp_01h7...');
console.log(cmp.status, cmp.sent_count, cmp.delivered_count);$cmp = $bt->campaigns->retrieve('cmp_01h7...');
echo $cmp->status, ' ', $cmp->sent_count, '/', $cmp->delivered_count, "\n";cmp = client.campaigns.get("cmp_01h7...")
puts cmp.status, cmp.sent_count, cmp.delivered_countcmp, _ := c.Campaigns.Get(context.Background(), "cmp_01h7...")
fmt.Println(cmp.Status, cmp.SentCount, cmp.DeliveredCount)curl https://api.blueticks.co/v1/campaigns/cmp_01h7... \
-H "Authorization: Bearer BLUETICKS_API_KEY"List all campaigns in the workspace with GET /v1/campaigns:
curl -X GET 'https://api.blueticks.co/v1/campaigns' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.campaigns.list()import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.campaigns.list();use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->campaigns->list();require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.campaigns.list()import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Campaigns.List(context.Background())Status lifecycle: pending → running → complete_sent → complete_delivered, with paused and aborted as side states.
For push updates, register a webhook subscribed to
campaign.started, campaign.paused, campaign.resumed,
campaign.completed, campaign.aborted.
4. Pause, resume, or cancel
# Pause a running campaign
bt.campaigns.pause("cmp_01h7...")
# Resume a paused campaign
bt.campaigns.resume("cmp_01h7...")
# Cancel permanently — no un-do
bt.campaigns.cancel("cmp_01h7...")// Pause a running campaign
await bt.campaigns.pause('cmp_01h7...');
// Resume a paused campaign
await bt.campaigns.resume('cmp_01h7...');
// Cancel permanently — no un-do
await bt.campaigns.cancel('cmp_01h7...');// Pause a running campaign
$bt->campaigns->pause('cmp_01h7...');
// Resume a paused campaign
$bt->campaigns->resume('cmp_01h7...');
// Cancel permanently — no un-do
$bt->campaigns->cancel('cmp_01h7...');# Pause a running campaign
client.campaigns.pause("cmp_01h7...")
# Resume a paused campaign
client.campaigns.resume("cmp_01h7...")
# Cancel permanently — no un-do
client.campaigns.cancel("cmp_01h7...")// Pause a running campaign
c.Campaigns.Pause(context.Background(), "cmp_01h7...")
// Resume a paused campaign
c.Campaigns.Resume(context.Background(), "cmp_01h7...")
// Cancel permanently — no un-do
c.Campaigns.Cancel(context.Background(), "cmp_01h7...")# Pause a running campaign
curl -X POST https://api.blueticks.co/v1/campaigns/cmp_01h7.../pause \
-H "Authorization: Bearer BLUETICKS_API_KEY"
# Resume a paused campaign
curl -X POST https://api.blueticks.co/v1/campaigns/cmp_01h7.../resume \
-H "Authorization: Bearer BLUETICKS_API_KEY"
# Cancel permanently — no un-do
curl -X POST https://api.blueticks.co/v1/campaigns/cmp_01h7.../cancel \
-H "Authorization: Bearer BLUETICKS_API_KEY"All three return 409 when the campaign isn't in a compatible state.
The resume and cancel transitions map to dedicated endpoints:
curl -X POST 'https://api.blueticks.co/v1/campaigns/string/resume' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.campaigns.resume( "id_01H7...",)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.campaigns.resume('id_01H7...');use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->campaigns->resume('id_01H7...');require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.campaigns.resume( "id_01H7...",)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Campaigns.Resume(context.Background(), "id_01H7...")curl -X POST 'https://api.blueticks.co/v1/campaigns/string/cancel' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.campaigns.cancel( "id_01H7...",)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.campaigns.cancel('id_01H7...');use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->campaigns->cancel('id_01H7...');require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.campaigns.cancel( "id_01H7...",)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Campaigns.Cancel(context.Background(), "id_01H7...")Limits to know
- Audience: max 1000 contacts per
createorappendrequest. Split larger lists client-side. - Variables: max 32 per contact, each ≤ 1024 chars, keys match
^[A-Za-z_][A-Za-z0-9_]{0,31}$. - Pacing is server-controlled — the internal send engine rate-limits per linked WhatsApp number. No customer-facing throttle knob.