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, onMissingVariable
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 — mediaUrl and its mediaCaption — attach an
image/video/document to every message. (The wire body is camelCase; the
Python, Ruby, PHP, and Go SDKs accept the snake_case forms shown in their tabs
and map them for you.)
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',
audienceId: 'aud_01h7...',
text: 'Hey {first_name}! Your order ships today.',
onMissingVariable: '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",
"audienceId": "aud_01h7...",
"text": "Hey {first_name}! Your order ships today.",
"onMissingVariable": "fail"
}'The response data includes live counters (sentCount, deliveredCount,
readCount, failedCount) plus status: "pending". (SDKs expose these under
their idiomatic names, e.g. Python's cmp.sent_count.)
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.sentCount, cmp.deliveredCount);$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.