Campaigns
Campaigns send one message (template) to many contacts with the care of respecting who receives it, caution built in. You pick recipients straight from your contact base, compose the message, and bZapper handles the pacing, the ramp-up and the health of your numbers — on its own.
Campaigns are a Pro plan add-on. On the unofficial rail they require, by default, 2
connected numbers past ramp-up (7+ days). With fewer, the API answers 409 campaign_needs_warmed_numbers. The minimum number count and the ramp-up window are
configurable per account (talk to support) — the default of 2 exists because
rotating across numbers is what reduces the block risk.
Pick recipients from your base (no spreadsheet, no JSON)
Instead of pasting a list, you search and select contacts with filters:
- Text search (name, phone, email);
- Filter by contact tags and groups;
- or "select all" matching the filter.
Only valid contacts are added: the server forces status = active and never
includes blocked, opted-out, unreachable or pending-validation contacts — no matter what
is requested. Suppression is re-checked at send time. It is impossible, through the
tool, to message someone who opted out.
In the API, POST /campaigns/{id}/recipients accepts:
{ "contact_ids": ["<uuid>", "..."] } // explicit selection
{ "contact_filter": { "tags": ["vip"], "groups": ["sp"] } } // everyone matching
{ "contact_filter": { "search": "maria" }, "replace": true } // replace the list (draft)
Phones are resolved server-side, only among active contacts. replace: true swaps
the whole list (before the campaign starts).
In an official API project
In an official project a campaign changes in three ways, all for the same reason — the number belongs to Meta, it is not paired by QR:
- Each variation is an approved template, not free text. Free text only reaches people who wrote in the last 24 hours, and a campaign is precisely for the ones who did not. Several template variations become an A/B test between templates.
- No ramp-up and no minimum number count. One connected number is enough. Meta limits
the volume (
TIER_250,TIER_1K… new contacts per day), and the limit rises on its own with quality. - No anti-block pacing. The 30–90 s intervals exist to protect QR numbers; on the Cloud API they would only delay delivery.
{
"name": "October promo",
"variations": [
{ "body": "", "template": { "name": "promo_october", "language": "en_US",
"variables": ["{nome}", "OCTOBER10"] } }
]
}
Everything is checked on creation: an unknown template returns 404 template_not_found, one
that is not approved returns 422 template_not_approved, and a variables count different
from what the body expects returns 422 template_variables_mismatch saying how many were
expected. Each variable accepts {column} from the list, like the text body. In the
dashboard, the new-campaign screen already shows the approved-template picker in an official
project.
Message: image + text, with variations
- Header image (optional): upload a PNG/JPEG/WebP (up to 5 MB) via
POST /campaigns/media— it returns a public URL to use invariations[].media. - Text with variables (
{name}): personalize per contact — use up to 3 variations to reduce the pattern and the block risk.
The builder shows a preview of how the message lands on WhatsApp and a real-time
summary: how many contacts will receive, how many numbers are eligible, and the
estimated send duration (GET /campaigns/estimate?recipients=&pacing=).
Trigger: manual or scheduled
- Manual — the campaign is saved as a draft; you click Start when you want to fire.
- Scheduled — pick a date and time; the campaign fires by itself at that time
(
start_atin the future).
While draft or scheduled, a campaign can be edited (name, pacing, schedule,
message and recipients) via PATCH /campaigns/{id}. Once started it becomes
view-only (409 if you try to edit).
Tracking
The campaign list shows, per campaign, live KPIs (sent, delivered, pending, failed) with a progress bar, plus a filter by status and by title.
Opening a running campaign shows the per-contact breakdown
(GET /campaigns/{id}/recipients): the contact name and the real delivery state —
sent, delivered and read (from WhatsApp receipts), plus failures (with reason)
and suppressed. Filter by Sent / Delivered / Read / Pending / Failed.
The sending controls
Under the hood, every send goes through the same controls:
- Controlled pacing (conservative or normal) with an interval between sends;
- Mandatory ramp-up — a new number (< 7 days) can't join;
- Load spreading (with 2+ numbers) to distribute the volume;
- Business-hours window;
- Smart auto-pause — if inferred blocking rises, the campaign pauses itself and the bad number is penalized.
You focus on the message and the list; bZapper handles pacing and distribution.