# Run a broadcast

> Send one approved template to a list of customers or to everyone with a tag, now or later, and follow it until it's done.

Source: https://docs.soosh.io/guides/api/run-a-broadcast/

A broadcast sends one template to many customers, each with their own
variables. It's the API's version of a campaign, and shows in
**Campaigns** in the app like any other. This guide sends the `diwali_offer`
template to Priya and Rahul.

## Before you start

- [An API key](https://docs.soosh.io/guides/integrations/create-an-api-key/) that can **Create, cancel and resume broadcasts** (`broadcasts:write`) and **Read broadcasts and recipients** (`broadcasts:read`)
- [An approved template](https://docs.soosh.io/guides/templates/get-a-template-approved/) on the number you send from. Authentication templates can't be broadcast.
- Room in your plan's broadcast messages for this month

Look up the template's `variables` first, as in
[Send a template](https://docs.soosh.io/guides/api/send-a-template/#1-look-up-the-templates-variables).
Every recipient fills in exactly those keys.

## 1. Choose who gets it

Send exactly one of these:

- **`recipients`**: a list of 1 to 10,000 phone numbers, each with its own
  variables. Use it when your system decides who gets the message.
- **`audience`**: your contacts with any of some tags, with the variables
  taken from each contact. Use it when Soosh already has the contacts.

Either way Soosh leaves out numbers that aren't valid, repeats of a number
already in the list, and, for a marketing template, contacts who opted out of
marketing messages. It counts them in the broadcast's `skipped`. Mexican and
Argentine mobiles count as one number however WhatsApp writes them (with or
without the `1` after 52, or the `9` after 54).

If that leaves nobody, a list is refused with `422 invalid_field`. An
audience is collected after the broadcast is made, so there the broadcast
becomes `failed` instead, with `paused_reason` saying why. The same happens
when a collected audience doesn't fit in this month's broadcast messages.

## 2. Create the broadcast

[Create a broadcast](https://docs.soosh.io/reference/api/broadcasts/create-broadcast/) with one of the two.

### To a list

```sh
curl https://app.soosh.io/v1/broadcasts \
  -H "Authorization: Bearer $SOOSH_API_KEY" \
  -H "Idempotency-Key: diwali-2026" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Diwali offer",
    "template": { "name": "diwali_offer", "language": "en" },
    "recipients": [
      { "to": "+919876543210", "name": "Priya", "body": { "1": "Priya" }, "buttons": { "0": "PRIYA10" } },
      { "to": "+919812345678", "name": "Rahul", "body": { "1": "Rahul" }, "buttons": { "0": "RAHUL10" } }
    ]
  }'
```

If any recipient's variables don't match the template, nothing is saved. The
answer is one `422` [`template_params`](https://docs.soosh.io/reference/api/errors/#template_params), and `details.rows` lists up to 100 bad
rows as `{index, missing, unexpected}`. The whole request can be at most
4 MB.

### To everyone with a tag

```sh
curl https://app.soosh.io/v1/broadcasts \
  -H "Authorization: Bearer $SOOSH_API_KEY" \
  -H "Idempotency-Key: diwali-2026-vip" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Diwali offer for VIPs",
    "template": { "name": "diwali_offer", "language": "en" },
    "audience": {
      "tags": ["VIP"],
      "exclude_tags": ["Unsubscribed"],
      "body": { "1": { "from": "contact.name", "fallback": "there" } },
      "buttons": { "0": { "from": "attribute:coupon", "fallback": "DIWALI10" } }
    }
  }'
```

Each variable says where its value comes from:

| `from` | The value |
|---|---|
| `contact.name` | The contact's name |
| `contact.phone` | The contact's number, as `+` and digits |
| `attribute:<name>` | One of the contact's attributes, like `attribute:coupon` |
| `text:<value>` | The same text for everyone, like `text:Diwali` |

`fallback` is used when a contact has no usable value. It's required for
`contact.name` and `attribute:` sources.

The audience is the contacts that already existed when you made the
broadcast, with the tags they have when Soosh collects them, a moment later.
A contact added after you made the broadcast isn't included, even if it has
the tag. A contact that already existed and is tagged after you made it is
included.

The broadcast comes back at once as `preparing` while Soosh collects the
audience. It then sends, or becomes `scheduled`, on its own.

## 3. Optional: send it later

Add `send_at`, 5 minutes to 30 days from now, in RFC 3339:

```json
"send_at": "2026-11-01T04:30:00Z"
```

The broadcast is `scheduled` until then. Its messages count against the
month it sends in.

## 4. Follow it

The answer to step 2 is the broadcast. Keep its `id`, then follow it in
either of these ways:

- **Webhooks:** subscribe an endpoint to
  [`broadcast.status`](https://docs.soosh.io/reference/api/webhook-events/broadcast-status/) to hear
  of every change of status. Add
  [`message.status`](https://docs.soosh.io/reference/api/webhook-events/message-status/) to hear
  about each message.
- **Asking:** [`GET /v1/broadcasts/{id}`](https://docs.soosh.io/reference/api/broadcasts/get-broadcast/) returns its `status`, `counts`
  (`total`, `sent`, `delivered`, `read`, `failed`, `replied`, `clicked`) and
  `skipped`.

To see what happened to each customer,
[list the recipients](https://docs.soosh.io/reference/api/broadcasts/list-broadcast-recipients/),
for example only the failed ones:

```sh
curl "https://app.soosh.io/v1/broadcasts/$BROADCAST_ID/recipients?status=failed" \
  -H "Authorization: Bearer $SOOSH_API_KEY"
```

A broadcast ends as `completed`, `cancelled`, `failed` or `missed`. See
[Campaign statuses](https://docs.soosh.io/reference/campaign-statuses/).

## 5. Pause, resume or cancel

A broadcast pauses on its own when something stops it, and `paused_reason`
says why, for example "This workspace has sent its 1,000 broadcast messages for October. Campaigns can be resumed from November 1, or contact support to raise the limit."

- **Resume:** [`POST /v1/broadcasts/{id}/resume`](https://docs.soosh.io/reference/api/broadcasts/resume-broadcast/) carries on with a `paused`
  broadcast. The template must still be approved, and the rest must fit in
  this month's broadcast messages.
- **Cancel:** [`POST /v1/broadcasts/{id}/cancel`](https://docs.soosh.io/reference/api/broadcasts/cancel-broadcast/) stops it for good. Messages
  already being sent finish; the rest are never sent.

> **Note:** A broadcast runs as the key that made it. If you revoke the key, or it loses
> `broadcasts:write`, the broadcast pauses with "API access for this broadcast
> was removed". Resume it with another key, or in the app.

## Limits

| Limit | Value |
|---|---|
| Recipients in a list | 1 to 10,000 |
| Request size | 4 MB |
| Tags in an audience | 1 to 20, and up to 20 to leave out |
| Broadcasts, templates and uploads made, and usage read | 10 a minute for each workspace, together |
| Broadcast messages | Your plan's `broadcast_messages_per_month`. Soosh checks before it starts, and a broadcast that reaches the limit while sending pauses. |

Broadcasts don't count against `api_messages_per_second`. See
[Rate limits](https://docs.soosh.io/reference/api/#rate-limits).

## Result

Priya and Rahul each get the offer with their own name and code. The
broadcast shows in **Campaigns** in the app, made by your key, and its
results update as WhatsApp reports them.

## Related

- [Create a broadcast](https://docs.soosh.io/reference/api/broadcasts/create-broadcast/)
- [Broadcasts API](https://docs.soosh.io/reference/api/broadcasts/)
- [The Broadcast object](https://docs.soosh.io/reference/api/broadcasts/#object)
- [Campaign statuses](https://docs.soosh.io/reference/campaign-statuses/)
- [How campaign pricing works](https://docs.soosh.io/concepts/how-campaign-pricing-works/)
- [Handle errors and limits](https://docs.soosh.io/guides/api/errors-and-limits/)
