Skip to main content

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.

Requirements

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.
Safe selection

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 in variations[].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_at in the future).
Editable until started

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.