Skip to content
Start free

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.

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 MARKETING or UTILITY category; 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.

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:

Terminal window
wabery contacts consent "contact_id" \
--categories MARKETING --source signup_form --method web_form

For a large audience, use Contacts → Import CSV or:

Terminal window
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 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"] },
});

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.

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

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.

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

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.