Blueticks

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_count
aud, _ := 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.status
cmp, _ := 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_count
cmp, _ := 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 create or append request. 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.