---
title: "WhatsApp Business API"
description: "Send WhatsApp templates, text, media and interactive messages through one multipart endpoint, manage templates and batches, and track every recipient."
canonical: "https://omnichannels.ai/developers/whatsapp/"
language: "en"
updated: "2026-10-10"
publisher: "BroadNet Technologies"
brand: "OmniChannels.ai"
section: "Messaging channels"
---

# WhatsApp Business API

Canonical: https://omnichannels.ai/developers/whatsapp/
Language: en
Publisher: OmniChannels.ai by BroadNet Technologies

All WhatsApp messages go through one endpoint, `POST /whatsapp/message/sendbulk`, where `messageType` selects the payload. This page shows how to send approved templates and session messages, manage templates, and track delivery per recipient.

## Before you send

- **Connect a WhatsApp Business Account.** Onboarding uses Meta Embedded Signup. `POST /whatsapp/generate-link` returns an encrypted signup link (HTTP `201`) for your account; the business admin opens it and completes Meta's flow (business account, phone number verification and display name). Our team supports you through Meta's review.
- **Have an approved template** for any message that starts a conversation or is sent outside the customer service window.
- **Hold opt-in** from each recipient, as Meta's policies require.

### The customer service window

When a customer messages you, a 24-hour customer service window opens. Inside it you can reply with any message type (text, media, interactive). Outside it, you can only send an approved template. Meta sets these rules and its pricing; check Meta's current documentation and rate card for the categories and markets you send to.

## Send a template

Set `messageType=TEMPLATE`, name the template and its language, and pass the body variable.

```bash title="cURL"
curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F campaignName=order-updates \
  -F destinationNo=971500000000 \
  -F messageType=TEMPLATE \
  -F templateName=order_update \
  -F templateLanguage=en_US \
  -F bodyTemplateVariables=INV-400
```

```js title="Node.js"
const form = new FormData();
form.append("batchId", "0");
form.append("campaignName", "order-updates");
form.append("destinationNo", "971500000000");
form.append("messageType", "TEMPLATE");
form.append("templateName", "order_update");
form.append("templateLanguage", "en_US");
form.append("bodyTemplateVariables", "INV-400");

const res = await fetch("https://api.broadnet.me/whatsapp/message/sendbulk", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.BROADNET_TOKEN}` }, // fetch sets the multipart boundary
  body: form,
});
console.log(await res.json());
```

```python title="Python"
import os
import requests

fields = {
    "batchId": "0",
    "campaignName": "order-updates",
    "destinationNo": "971500000000",
    "messageType": "TEMPLATE",
    "templateName": "order_update",
    "templateLanguage": "en_US",
    "bodyTemplateVariables": "INV-400",
}
# Send every field as a multipart part (files=) rather than url-encoded (data=).
r = requests.post(
    "https://api.broadnet.me/whatsapp/message/sendbulk",
    headers={"Authorization": f"Bearer {os.environ['BROADNET_TOKEN']}"},
    files={k: (None, v) for k, v in fields.items()},
    timeout=30,
)
print(r.json())
```

```php title="PHP"
<?php
$ch = curl_init("https://api.broadnet.me/whatsapp/message/sendbulk");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("BROADNET_TOKEN")],
    // An array (not a string) makes cURL send multipart/form-data.
    CURLOPT_POSTFIELDS => [
        "batchId" => "0",
        "campaignName" => "order-updates",
        "destinationNo" => "971500000000",
        "messageType" => "TEMPLATE",
        "templateName" => "order_update",
        "templateLanguage" => "en_US",
        "bodyTemplateVariables" => "INV-400",
    ],
]);
echo curl_exec($ch);
curl_close($ch);
```

The call queues the messages and returns at once with a batch summary:

```json title="Response"
{
  "totalFiles": 1,
  "totalSuccess": 1,
  "totalFailed": 0,
  "totalDuplicates": 0,
  "batchId": "2609091405000000001",
  "fileResults": [
    {
      "fileName": "direct",
      "totalRecords": 1,
      "successCount": 1,
      "failedRecipients": [],
      "duplicateRecipients": []
    }
  ],
  "message": "Messages queued for delivery"
}
```

Store `batchId`. Per-recipient statuses arrive on your webhook and can be read from the bulk insights endpoint.

## Request fields

Which fields are required depends on `messageType`. These are the common ones:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `messageType` | string | Yes | `TEXT`, `IMAGE`, `VIDEO`, `AUDIO`, `DOCUMENT`, `STICKER`, `LOCATION`, `CONTACTS`, `TEMPLATE`, `INTERACTIVE`, `REACTION` or `FLOW`. |
| `destinationNo` | string | One of these two | One or more numbers with country code, separated by commas, semicolons or new lines. |
| `destinationFiles` | file | One of these two | A TXT, CSV or XLSX file of recipients. For personalised templates, one row per recipient with its variables. |
| `batchId` | string | No | `0` starts a new batch. |
| `campaignName` | string | No | Your label for reporting. |
| `allowDuplicate` | string | No | `false` removes duplicate recipients. |
| `content` | string | Per type | Text body (`TEXT`, up to 4,096 characters) or caption for media. |
| `mediaUrl` | string | Per type | Public HTTPS URL of the media. |
| `mediaFile` | file | Per type | Upload the media instead of linking it. |
| `templateName` | string | `TEMPLATE` | Approved template name. |
| `templateLanguage` | string | `TEMPLATE` | Template language code, for example `en_US` or `ar`. |
| `bodyTemplateVariables` | string | Per template | Body variable value. Named parameters, currency and date-time values are passed as JSON. |
| `headerTemplateVariables` | string | Per template | Header variable (text, or product details for product headers). |
| `buttonsTemplateVariable` | string | Per template | Dynamic button value: URL suffix, coupon code or OTP code. |
| `interactivePayload` | string (JSON) | `INTERACTIVE` | Meta's interactive object: buttons, list, CTA URL, location request and more. |
| `contactsPayload` | string (JSON) | `CONTACTS` | Array of contact cards. |
| `latitude`, `longitude`, `locationName`, `locationAddress` | string | `LOCATION` | Location pin. |
| `replyToMessageId` | string | No | WhatsApp message ID (`wamid`) to quote-reply to, or to react to with `REACTION`. |
| `emoji` | string | `REACTION` | The reaction emoji. |
| `delay` | string | No | Throttle between messages. |
| `scheduled`, `scheduleTime`, `gmt` | string | No | Schedule the batch, for example `scheduleTime=2026-11-01 10:00:00.000`, `gmt=+04:00`. |
| `recurrence`, `repeatCount`, `recurrenceEndTime` | string | No | Recurring sends, for example `DAILY`. |

For templates with several variables per recipient, upload a personalised `destinationFiles` file with one row per recipient. Confirm the column layout with our team for your first campaign.

### Template variations

| Template feature | Fields to add |
| --- | --- |
| Header image, video or document | `mediaUrl` (overrides the header media) |
| Header text variable | `headerTemplateVariables` |
| Dynamic URL button | `buttonsTemplateVariable=summer2026` |
| Authentication (OTP) with copy-code button | `bodyTemplateVariables=482913` and `buttonsTemplateVariable=482913` |
| Coupon (copy code) | `buttonsTemplateVariable=SAVE20` |
| Several buttons | `buttonsTemplateVariable` as a JSON array with `index`, `sub_type` and `value` |
| Carousel | `carouselSendCards[0].bodyVariables[0]` and similar per-card fields |
| Named parameter | `bodyTemplateVariables={"parameter_name":"order_id","text":"ORD-12345"}` |

## Send session messages

Inside the service window you can send free-form messages. Two examples:

```bash title="Text"
curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F destinationNo=971500000000 \
  -F messageType=TEXT \
  -F campaignName=support \
  -F "content=Your order has shipped. Reply here if you need anything."
```

```bash title="Reply buttons"
curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F destinationNo=971500000000 \
  -F messageType=INTERACTIVE \
  -F campaignName=support \
  -F 'interactivePayload={"type":"button","body":{"text":"Confirm your delivery slot?"},"action":{"buttons":[{"type":"reply","reply":{"id":"slot_ok","title":"Confirm"}},{"type":"reply","reply":{"id":"slot_change","title":"Change"}}]}}'
```

Interactive payloads follow Meta's interactive message format, which also covers lists, CTA URL buttons, location requests, product and catalog messages, and Flows.

### Mark a message as read

`POST /whatsapp/message/read` marks an inbound message as read. Add `"typing": true` to show a typing indicator while you prepare the reply.

```bash title="cURL"
curl -X POST https://api.broadnet.me/whatsapp/message/read \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "messageId": "wamid.HBgMOTcxNTAwMDAwMDAwFQIAEhgg", "typing": true }'
```

## Track delivery

Each recipient moves through `SUBMITTED`, `SENT`, `DELIVERED` and `READ`, or ends as `FAILED` or `REJECTED` with a reason. Statuses are pushed to your account's webhook. You can also read them per batch:

```bash title="cURL"
curl "https://api.broadnet.me/whatsapp/message/bulk-insights?batch_Id=2609091405000000001&status=FAILED&page=0&size=50" \
  -H "Authorization: Bearer $BROADNET_TOKEN"
```

| Parameter | Required | Description |
| --- | --- | --- |
| `batch_Id` | Yes | Batch ID returned by the send. |
| `status` | No | Filter, for example `DELIVERED`, `READ` or `FAILED`. |
| `destination` | No | Filter to one recipient. |
| `search` | No | Free-text search. |
| `page`, `size` | No | Zero-based page index and page size (default `0` and `10`). |

## Manage batches

| Method | Path | Description |
| --- | --- | --- |
| POST | `/whatsapp/message/all-bulk` | List batches with filters (multipart form fields such as `page`, `size`, `status`, `campaignName`). |
| GET | `/whatsapp/message/edit?batch_Id=…` | Batch details. |
| GET | `/whatsapp/message/pause?batch_Id=…` | Pause a batch. |
| GET | `/whatsapp/message/resume?batch_Id=…` | Resume a paused batch. |
| DELETE | `/whatsapp/message/abort?batch_Id=…` | Abort a batch. |

## Manage templates

Templates are created through the API (or the console), reviewed by Meta, and then sent by name.

| Method | Path | Description |
| --- | --- | --- |
| POST | `/whatsapp/template` | Create a template (multipart). |
| GET | `/whatsapp/template/list` | List templates. Filters: `name`, `status` (`APPROVED`, `PENDING`, `REJECTED`, `PAUSED`), `language`, `category`, `page`, `size`. |
| GET | `/whatsapp/template?name=…` | Template details by name. |
| GET | `/whatsapp/template/grouped-by-category` | Templates grouped by category. |
| PUT | `/whatsapp/template/update/{WA_TEMPLATE_ID}` | Edit a template. |
| DELETE | `/whatsapp/template?name=…` | Delete a template by name. |
| POST | `/whatsapp/template/refresh?name=…` | Re-sync status and category from Meta. |
| POST | `/whatsapp/template/library` | Create a template from Meta's template library. |

Main fields for `POST /whatsapp/template`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | Lowercase name, unique in your account. |
| `languageType` | string | Yes | Language code, for example `en_US`. |
| `category` | string | Yes | `MARKETING`, `UTILITY` or `AUTHENTICATION`. |
| `templateType` | string | Yes | For example `DEFAULT`, `UTILITY_DEFAULT`, `OTP`, `CATALOG` or `CALLING_PERMISSION`. |
| `headerFormat` | string | No | `NONE`, `TEXT`, `IMAGE`, `VIDEO`, `DOCUMENT`, `LOCATION` or `PRODUCT`. |
| `headerText`, `headerFile` | string, file | Per header | Header text, or a sample media file. |
| `bodyText` | string | Yes (except OTP) | Body with numbered placeholders. |
| `bodyExample` | string (JSON) | With variables | Sample values, for example `[["Sara","ORD-12345"]]`. |
| `footerText` | string | No | Footer text. |
| `buttonRequest` | string | No | Button definition, for example `QUICK_REPLY:Track order` or `URL:Track\|https://example.com/track/{{1}}\|ORD123`. |
| `deliveryMode` | string | OTP | `COPY_CODE`, `ONE_TAP` or `ZERO_TAP` for authentication templates. |
| `codeExpirationMinutes` | string | No | OTP expiry shown to the user. |

New templates start as `PENDING` until Meta approves them. If you suspect a missed status update, call the refresh endpoint.

## Other WhatsApp features in the API

The WhatsApp section of the API also covers:

- **WhatsApp Flows**: create, upload, publish and measure Flows (for example `POST /whatsapp/flow/create`, `POST /whatsapp/flow/{WA_FLOW_ID}/publish`, `GET /whatsapp/flow/{WA_FLOW_ID}/metrics`).
- **Catalogs**: manage products and product sets on a connected catalog (for example `POST /whatsapp/catalog/{WA_CATALOG_ID}/product`, `POST /whatsapp/catalog/connect`) and read `GET /whatsapp/message/catalog-insights`.
- **QR links**: create and manage QR codes that open a chat (`/whatsapp/qr-links`).
- **Business profile**: read and update the profile (`/whatsapp/business-profile`) and upload a profile photo (`POST /whatsapp/profile-photo`).
- **Blocked users**: list, block and unblock users (`/whatsapp/block-users`).

The full field reference for each is in the OpenAPI spec, available to customers on request. See [SDKs and OpenAPI](/developers/sdks/).

## Next steps

- [Webhooks](/developers/webhooks/): handle `DELIVERED`, `READ` and `FAILED` events.
- [RCS](/developers/rcs/): reach Android users with rich cards, with SMS fallback.
- [Errors and limits](/developers/errors-and-limits/): handle `502` errors that carry Meta's error code.
- [SDKs and OpenAPI](/developers/sdks/): send WhatsApp messages with the TypeScript SDK.
