> ## Documentation Index
> Fetch the complete documentation index at: https://docs.60db.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# WhatsApp broadcasts

> Prepare consent-based campaigns, schedule approved templates and cancel pending sends

Broadcasts use the native 60db message queue. Authenticate with a 60db API key with `whatsapp` scope. An active **Starter, Creator or Pro platform plan** and an owner, admin or developer workspace role are required to create, schedule or cancel a campaign. Members and viewers can read campaigns and recipient outcomes. Flow plans are excluded.

Every recipient must be an existing contact on the campaign's number with an explicit, current opt-in. A previous inbound message does not satisfy broadcast consent. Only synced `APPROVED` templates can be used.

## Prepare a draft

`POST /whatsapp/broadcasts` requires an `Idempotency-Key` header, using 8–128 letters, digits, dots, colons, underscores or hyphens. Retry the identical request with the same key after a lost response. A different request with an existing key returns `409 IDEMPOTENCY_CONFLICT`.

```json theme={null}
{
  "number_id": "11111111-1111-4111-8111-111111111111",
  "name": "October customer update",
  "template": {
    "name": "customer_update",
    "language": {"code": "en_US"},
    "components": [{"type": "body", "parameters": [{"type": "text", "text": "October"}]}]
  },
  "recipients": [
    {"contact_id": "22222222-2222-4222-8222-222222222222"},
    {
      "contact_id": "33333333-3333-4333-8333-333333333333",
      "components": [{"type": "body", "parameters": [{"type": "text", "text": "Your October order"}]}]
    }
  ]
}
```

Supply 1–1,000 unique contacts. Split larger audiences into separate campaigns. Recipient `components` replace the campaign's entire component array for that recipient; omission uses the campaign components. Template name and language stay the same. Recipient phone numbers, fully resolved template components and rendered text previews are saved at creation, along with the synced approved template definition.

Creation returns `201` with `data.id`, `state: "draft"`, name, number, template and timestamps. Creating a draft sends nothing. The audience and payload are immutable: cancel a draft and create a new one to change them. Reusing the creation key returns the same campaign.

Template media must have been [uploaded to this number](https://docs.60db.ai/api-reference/whatsapp/media.md). Media ownership and expiry are checked at preparation, scheduling and dispatch. A future schedule does not extend the media lifetime; expired attachments will be skipped or fail before dispatch.

## Schedule or send now

`POST /whatsapp/broadcasts/{id}/schedule` accepts an explicit time zone:

```json theme={null}
{"scheduled_at": "2026-10-09T10:00:00+05:30"}
```

Send `{}` to start as soon as the worker runs. A supplied time must be in the future. The response is `202` with `state: "scheduled"`; it does not confirm delivery. Repeating the identical scheduling body returns the accepted schedule without a second campaign. A different schedule after the draft has been accepted returns `409 BROADCAST_NOT_DRAFT`.

The durable worker resumes scheduled/running campaigns after a restart. Each recipient is queued once. It rechecks the original creator's live membership, API key, platform plan and broadcast management permission; these are also checked before provider dispatch. Scheduling another teammate's draft does not replace the recorded creator. Revocation stops the campaign. A temporary authorization outage waits without sending.

Current consent, number connection, media ownership and template approval are rechecked. The approved template definition must also match the saved definition: scheduling returns `409 BROADCAST_TEMPLATE_CHANGED` when it differs; queuing/dispatch record the failure against the affected recipient. Sync templates and prepare a new draft after reviewing the changed definition. An individual recipient who loses consent before queuing is skipped; consent revoked after queuing blocks dispatch. Template failures and other definite dispatch failures are recorded against each recipient. Broadcasts do not take over or pause the number's assistant.

## Cancel

`POST /whatsapp/broadcasts/{id}/cancel` stops a draft, future schedule or running campaign. Cancellation is idempotent for a cancelled campaign. Completed/failed campaigns return `409 BROADCAST_FINISHED`.

Cancellation waits for a provider call already in progress. When it returns, no remaining campaign message can start dispatch. Already accepted messages remain sent and can still receive delivery/read updates; cancellation cannot recall them. A send left in progress by an interrupted worker remains `unknown`, because it may already have reached the provider. Unknown sends are never automatically retried.

## Inspect progress

| Method | Path | Result |
| - | - | - |
| GET | `/whatsapp/broadcasts` | Campaigns with counts grouped by current recipient/message status; optional `number_id` |
| GET | `/whatsapp/broadcasts/{id}` | One campaign's configuration, lifecycle state and complete recipient counts |
| GET | `/whatsapp/broadcasts/{id}/recipients` | Contact identity, frozen content, `preview_text`, message ID, current status and error code |

Both lists accept `limit` (1–100, default 50) and `offset` (0–100,000) and return `pagination: {limit, offset, has_more}`. All reads are scoped to the authenticated workspace. Campaign responses omit internal request hashes and the internal template definition snapshot. Campaign `counts` cover the complete audience, independently of recipient pagination. `preview_text` renders saved text sections and supplied text variables; it does not render media or interactive content, whose exact parameters remain in `content`.

Campaign states are `draft`, `scheduled`, `running`, `completed`, `cancelled` and `failed`. **Completed means all dispatch attempts are settled, not that every message was delivered.** Counts and recipient rows distinguish `pending`, `skipped`, `cancelled`, `queued`, `sending`, `sent`, `delivered`, `read`, `failed` and `unknown`. Later provider callbacks can update completed campaign counts.

Use the recipient's `message_id` with [message lookup and status webhooks](https://docs.60db.ai/api-reference/whatsapp/send-message.md). An ambiguous provider response or failure to persist an accepted result remains unconfirmed. Check the conversation before choosing to send again; do not recreate the campaign as a blind retry.

## Billing and errors

Broadcast access is included in paid platform plans. Meta messaging charges follow the connected business's existing Meta billing arrangement. Broadcast template sending does not run the AI pipeline or deduct native AI credits.

| HTTP | Code | Recovery |
| - | - | - |
| 400 | `VALIDATION_ERROR` / `IDEMPOTENCY_KEY_REQUIRED` | Correct the request |
| 400 | `BROADCAST_RECIPIENT_INVALID` | Choose existing contacts from the sending number and workspace |
| 400 | `BROADCAST_SCHEDULE_INVALID` | Use a future time with an explicit time zone, or omit it to send now |
| 403 | `POLICY_DENY` | Check role, active platform plan and WhatsApp scope |
| 404 | `BROADCAST_NOT_FOUND` | Use a campaign ID from this workspace |
| 409 | `BROADCAST_OPT_IN_REQUIRED` | Obtain and record explicit consent for every selected contact |
| 409 | `TEMPLATE_NOT_APPROVED` | Sync and choose an approved template |
| 409 | `BROADCAST_TEMPLATE_CHANGED` | Sync templates, review the changed definition and prepare a new draft |
| 409 | `NUMBER_DISCONNECTED` | Reconnect before preparing or scheduling |
| 409 | `BROADCAST_NOT_DRAFT` / `BROADCAST_FINISHED` | Inspect the campaign's current lifecycle state |
| 409 | `IDEMPOTENCY_CONFLICT` | Use a new key for a different campaign |

The `broadcast:` idempotency-key prefix is reserved for internal campaign messages and cannot be used with `POST /whatsapp/messages`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.