WhatsApp broadcasts
A broadcast is a one-time, template-based WhatsApp send to as many as 100,000 contacts. Wabery resolves each contact’s preferred language, snapshots the eligible audience, and sends it through a dedicated Meta Cloud API channel. You can build and monitor the same lifecycle in the dashboard, SDK, CLI, REST API, or MCP server.
Before you send
Section titled “Before you send”You need:
- a connected, dedicated Meta Cloud API channel with acceptable quality and a confirmed Meta payment method;
- an approved template variant for every supported language and a fallback;
- active consent for the broadcast’s
MARKETINGorUTILITYcategory; and - no active contact suppression.
Marketing broadcasts default to at most one send per contact in 24 hours and three in seven days. Wabery checks consent, suppressions, blocks, language, and frequency both when preparing the audience and immediately before each send. Dispatch also applies a Redis-backed per-channel messages-per-second limit, so one busy phone number cannot consume another channel’s provider allowance.
The dashboard shows the precise reason a channel cannot be selected. API
clients receive meta_payment_method_required after a successful check confirms
billing is missing, or meta_payment_status_unavailable when Wabery could not
verify the current state. Treat the latter as an instruction to refresh channel
status, not proof that billing is absent.
Add the audience
Section titled “Add the audience”For a small audience, add contacts individually in Contacts → Add contact,
with the API/SDK, or with wabery contacts enroll. Record MARKETING or
UTILITY consent separately when it was not captured during enrollment:
wabery contacts consent "contact_id" \ --categories MARKETING --source signup_form --method web_formFor a large audience, use Contacts → Import CSV or:
wabery contacts import contacts.csv \ --phone-column phone \ --name-column name \ --language-column language \ --tags-column tags \ --consent '{"categories":["MARKETING"],"source":"signup_form","method":"web_form"}'
wabery contacts import-status "import_id"CSV files may contain up to 100,000 rows and 25 MB. Phone numbers must be E.164.
Tags in a mapped column are separated with | or ;. Only include the
--consent declaration when the same declaration is true and supportable for
every row; otherwise import the contacts without it and record consent from its
actual source.
See Contacts & opt-in for complete import, consent, suppression, and evidence guidance.
Create and prepare
Section titled “Create and prepare”Create the draft with one or more approved variants, then select exactly one audience source: contact ids, a completed import, or a server-side filter.
const broadcast = await wabery.broadcasts.create({ name: "August launch", channelId: "channel_...", category: "MARKETING", templateName: "august_launch", fallbackLanguage: "en_US", variants: [{ templateId: "template_...", language: "en_US", parameterMappings: [{ component: "body", index: 0, parameterType: "text", source: "name", fallback: "there", }], }],});
await wabery.broadcasts.prepareAudience(broadcast.id, { filter: { tags: ["launch-list"], languages: ["en", "es"] },});wabery broadcasts create --body @broadcast.jsonwabery broadcasts prepare broadcast_123 --body @audience.jsonwabery broadcasts get broadcast_123curl https://api.wabery.com/v1/broadcasts \ -H "Authorization: Bearer $WABERY_API_KEY" \ -H "Idempotency-Key: launch-2026-create" \ -H "Content-Type: application/json" \ --data @broadcast.json
curl https://api.wabery.com/v1/broadcasts/broadcast_123/audience \ -H "Authorization: Bearer $WABERY_API_KEY" \ -H "Idempotency-Key: launch-2026-audience" \ -H "Content-Type: application/json" \ -d '{"filter":{"tags":["launch-list"]}}'Preparation is asynchronous. Poll GET /v1/broadcasts/{id} until the status is
ready, then review the total and skipped counts. The prepared list is a frozen
snapshot: later tag, filter, or import changes do not silently change who will
receive the campaign.
An audience.json file has one of these shapes:
{ "contactIds": ["contact_1", "contact_2"] }{ "importId": "import_..." }{ "filter": { "search": "Ada", "tags": ["launch-list"], "languages": ["en", "es"], "isBlocked": false, "createdAfter": "2026-01-01T00:00:00Z" }, "excludeContactIds": ["contact_..."]}Use GET /v1/broadcasts/{id}/recipients or
wabery broadcasts recipients <id> to audit selected language, skip reason,
failure, and delivery status. The endpoint is cursor-paginated and accepts a
recipient status filter.
Lifecycle
Section titled “Lifecycle”| Status | Meaning | Valid next action |
|---|---|---|
draft |
Template configuration exists; no audience snapshot yet. | Prepare, cancel, or delete. |
preparing |
Wabery is resolving consent, suppressions, language, and safeguards. | Poll. |
ready |
The frozen snapshot is available for review. | Send, schedule, duplicate, or cancel. |
scheduled |
Dispatch will start at the absolute scheduled instant. | Monitor or cancel. |
sending |
Eligible recipients are being dispatched. | Monitor or cancel remaining work. |
completed |
Every recipient reached a terminal state. | Inspect or duplicate. |
canceled |
Remaining work was stopped. | Inspect or duplicate. |
failed |
Campaign-level preparation or dispatch failed. | Inspect failure details or duplicate. |
Send or schedule
Section titled “Send or schedule”await wabery.broadcasts.send(broadcast.id);
// Or schedule an absolute instant and retain the display timezone:await wabery.broadcasts.schedule(broadcast.id, { scheduledAt: "2026-08-01T10:00:00Z", timezone: "Europe/Amsterdam",});Mutating broadcast API calls require Idempotency-Key; the SDK and CLI generate
one automatically. A repeated key with the same request replays the original
response, while reuse with different content returns 409.
Sending is irreversible after Meta accepts a recipient message. Canceling a scheduled or sending broadcast only stops recipients that have not yet been dispatched. Duplicate a previous broadcast to create a fresh draft with the same template configuration; its old audience is intentionally not copied.
Wabery stores a send-attempt marker immediately before calling the provider. If a worker loses the response and cannot determine whether Meta accepted the message, it records an acceptance-unknown failure for reconciliation instead of automatically sending a possible duplicate.
Use an AI agent (MCP)
Section titled “Use an AI agent (MCP)”The Wabery MCP server exposes draft, preparation, review, delivery, scheduling, cancellation, and duplication tools. A safe prompt is:
On hosted MCP, send, schedule, and cancel open a client-controlled confirmation request. The operation only proceeds when the person accepts it. Local stdio MCP keeps confirmation-token fallback behavior when it is explicitly launched in write mode. See the MCP guide for the complete tool map.
REST endpoints and scopes
Section titled “REST endpoints and scopes”| Operation | Endpoint | Scope |
|---|---|---|
| List / create | GET /broadcasts, POST /broadcasts |
broadcasts:read / broadcasts:write |
| Inspect | GET /broadcasts/{id} |
broadcasts:read |
| Prepare | POST /broadcasts/{id}/audience |
broadcasts:write |
| Recipients | GET /broadcasts/{id}/recipients |
broadcasts:read |
| Send / schedule | POST /broadcasts/{id}/send, POST /broadcasts/{id}/schedule |
broadcasts:send |
| Cancel / duplicate | POST /broadcasts/{id}/cancel, POST /broadcasts/{id}/duplicate |
broadcasts:write |
| Delete a draft | DELETE /broadcasts/{id} |
broadcasts:write |
Opt-outs and delivery events
Section titled “Opt-outs and delivery events”Mandatory inbound keywords STOP, UNSUBSCRIBE, CANCEL, END, and QUIT
cannot be removed by project settings. Wabery also recognizes common explicit
phrases such as “please stop” and “do not message me”, ignoring case, surrounding
whitespace, and trailing punctuation. Recording consent never lifts a
suppression. Re-enrollment requires an explicit suppression-lift action after
new consent has been independently verified.
Configured project webhooks receive broadcast.started,
broadcast.completed, broadcast.canceled, and broadcast.failed.
message.status also includes broadcast_id and broadcast_recipient_id for
recipient-level delivery correlation.
Treat accepted as provider acceptance, not delivery. Use recipient-level
delivered, read, failed, skipped, and canceled states for reporting.