Broadcasts
Send an approved WhatsApp template to many recipients with per-recipient variables, now or at a scheduled time, with delivery tracking per recipient.
By the end of this page, a template message has gone out to a list of phone numbers, each with its own variable values. You can read who was reached.
Before you start
You need a connected WhatsApp account, its accountId and profileId, and an APPROVED template (Templates).
Each recipient counts as one outbound message. Meta's template fee is separate and goes to your WABA (who bills what).
Meta's messaging-limit tier caps how many recipients actually deliver. A freshly connected number is on TIER_250, 250 unique contacts per day, and a larger list silently stops there.
Read the number's current tier with Number status.
Step 1: Create the broadcast
Call POST /v1/broadcasts with platform: "whatsapp", the account, and a template naming an approved template. The broadcast is created as a draft.
import Zernio from '@zernio/node';
const zernio = new Zernio();
const { data: created } = await zernio.broadcasts.createBroadcast({
body: {
profileId: '66a1f0c2a4b9d3e8f1a2b3c4',
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
platform: 'whatsapp',
name: 'Order confirmations',
template: {
name: 'order_confirmation',
language: 'en',
components: [{
type: 'body',
parameters: [
{ type: 'text', text: '{{1}}' },
{ type: 'text', text: '{{2}}' }
]
}],
variableMapping: {
'1': { field: 'name' },
'2': { field: 'custom', customValue: 'VIP-2027' }
}
}
}
});
const broadcastId = created.broadcast.id;Response (200):
{
"success": true,
"broadcast": {
"id": "66d4a1b2c3e4f5a6b7c8d9e2",
"name": "Order confirmations",
"platform": "whatsapp",
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"status": "draft",
"createdAt": "2027-01-01T09:00:00Z"
}
}broadcast.id is the broadcastId for the next steps. A template with no variables needs only name and language.
Template variables
A template body holds numbered placeholders, {{1}}, {{2}} and so on, fixed when the template was created (Hi {{1}}, your order {{2}} has been confirmed.).
Broadcasts fill numbered placeholders only, not named ones.
variableMapping maps each position to a contact field or a fixed value. Zernio resolves it per recipient at send time, so {{1}} becomes each contact's own name:
field | Resolves to |
|---|---|
name | The contact's name (falls back to "there" if unset) |
phone | The recipient's phone number |
email | The contact's email |
company | The contact's company |
custom | The literal customValue string, the same for every recipient |
The number of variableMapping entries must match the number of placeholders in the template body. A mismatch makes Meta reject the send with a parameter-count error (code 132000).
Step 2: Add recipients
Call POST /v1/broadcasts/{broadcastId}/recipients with one of:
phonesin E.164 format. Contacts are created for numbers Zernio has not seen.contactIdsfor existing contacts.useSegment: trueto pull every contact matching the broadcast'ssegmentFilters.
segmentFilters takes tags, isSubscribed and customFields.
customFields is an object of custom field values keyed by field slug. Every key must match exactly, type included, so 5 does not match "5".
const { data: recipients } = await zernio.broadcasts.addBroadcastRecipients({
path: { broadcastId },
body: { phones: ['+13105551234', '+442071234567'] }
});
console.log(recipients.added, recipients.skipped);Response (200):
{
"success": true,
"added": 2,
"skipped": 0
}skipped counts duplicates and contacts with no WhatsApp channel.
To add existing contacts instead, send { "contactIds": ["66e5b2c3d4f5a6b7c8d9e0f1"] } with Zernio contact ids, not phone numbers.
To fill from a segment, send { "useSegment": true }.
Step 3: Send now or schedule
Call POST /v1/broadcasts/{broadcastId}/send to start delivery at once.
const { data: sent } = await zernio.broadcasts.sendBroadcast({
path: { broadcastId }
});
console.log(sent.status, sent.sent, sent.failed);Response (200), after the first batch:
{
"success": true,
"status": "sending",
"sent": 2,
"failed": 0,
"recipientCount": 2
}To send later, call POST /v1/broadcasts/{broadcastId}/schedule with the scheduled time instead:
const { data: scheduled } = await zernio.broadcasts.scheduleBroadcast({
path: { broadcastId },
body: { scheduledAt: '2027-01-01T12:00:00Z' }
});
console.log(scheduled.broadcast.status);Response (200):
{
"success": true,
"broadcast": {
"id": "66d4a1b2c3e4f5a6b7c8d9e2",
"status": "scheduled",
"scheduledAt": "2027-01-01T12:00:00Z"
}
}Delivery is tracked per recipient as sent, delivered and read.
Read it with List broadcast recipients. Or receive it as it happens through the message.delivered and message.read events (WhatsApp inbox webhooks).
If it fails
A 400 from the send or schedule call means the broadcast has no recipients:
{
"error": "Broadcast has no recipients"
}Add them with Step 2.
The other 400 on those calls reads Cannot send broadcast in sending status and names the status it refused. Check status with Get broadcast before retrying.
Per-recipient failures surface in failed and on the recipient list, not as an error on the call. The common cause is a template whose placeholder count does not match variableMapping (Meta code 132000).
Every error uses the envelope in error handling.
Related
- Templates: create the template and watch for Meta's approval.
- Contacts: import contacts with tags so
useSegmentcan target them. - Broadcasts API: every field, plus cancel and list.
- Messages pricing: how broadcast recipients meter.
- WhatsApp inbox: send a template into a single conversation instead.