Messaging API
Send to Multiple Numbers API
Send the same WhatsApp message to several phone numbers and groups in one request. Use it for small broadcasts such as newsletters, offers and announcements.
https://wbiztool.com/api/v1/send_msg/multi/Body: JSON or form fields
Wbiztool creates one message per recipient and returns a msg_id for each, so you can check their status individually.
Quick example#
curl -X POST https://wbiztool.com/api/v1/send_msg/multi/ \
-H "Content-Type: application/json" \
-d '{
"client_id": 12345,
"api_key": "YOUR_API_KEY",
"whatsapp_client": 678,
"msg_type": 0,
"country_code": "91",
"phone": "9876543210,9812345670,Sales Team Mumbai",
"msg": "Our Diwali sale starts tomorrow. Get 20% off on all orders!"
}'import requests
response = requests.post(
"https://wbiztool.com/api/v1/send_msg/multi/",
json={
"client_id": 12345,
"api_key": "YOUR_API_KEY",
"whatsapp_client": 678,
"msg_type": 0,
"country_code": "91",
"phone": ",".join(["9876543210", "9812345670", "Sales Team Mumbai"]),
"msg": "Our Diwali sale starts tomorrow. Get 20% off on all orders!",
},
timeout=60,
)
result = response.json() # read the body even when the HTTP code is 400
if result.get("status") == 1:
for item in result["messages"]:
print(item["contact"], "->", item["msg_id"])
else:
print("Failed:", result.get("message", "no message in response"))// Node.js 18+ (built-in fetch). Save as .mjs to use top-level await.
const response = await fetch("https://wbiztool.com/api/v1/send_msg/multi/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: 12345,
api_key: "YOUR_API_KEY",
whatsapp_client: 678,
msg_type: 0,
country_code: "91",
phone: ["9876543210", "9812345670", "Sales Team Mumbai"].join(","),
msg: "Our Diwali sale starts tomorrow. Get 20% off on all orders!",
}),
});
const result = await response.json(); // read the body even when the HTTP code is 400
if (result.status === 1) {
for (const item of result.messages) {
console.log(item.contact, "->", item.msg_id);
}
} else {
console.error("Failed:", result.message ?? "no message in response");
}<?php
$payload = [
'client_id' => 12345,
'api_key' => 'YOUR_API_KEY',
'whatsapp_client' => 678,
'msg_type' => 0,
'country_code' => '91',
'phone' => implode(',', ['9876543210', '9812345670', 'Sales Team Mumbai']),
'msg' => 'Our Diwali sale starts tomorrow. Get 20% off on all orders!',
];
$ch = curl_init('https://wbiztool.com/api/v1/send_msg/multi/');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($result['status'] ?? 0) === 1) {
foreach ($result['messages'] as $item) {
echo $item['contact'] . ' -> ' . $item['msg_id'] . PHP_EOL;
}
} else {
echo 'Failed: ' . ($result['message'] ?? 'no message in response');
}Replace 12345, YOUR_API_KEY and 678 with your own values. See Authentication for where to find them.
Request parameters#
Authentication
client_idintegerrequiredYour API Client ID from Settings → API keys.
api_keystringrequiredYour API key from the same page.
whatsapp_clientintegerrequiredID of the WhatsApp number to send from, from WhatsApp settings. Unlike Send message, this endpoint never picks a number for you.
Recipients and message
phonestringrequiredPhone numbers and group names in one comma-separated string, for example
9876543210,9812345670,Sales Team Mumbai. Don't send a JSON array. See How recipients are read.country_codestringoptionalCountry calling code without
+, for example91. It's added in front of each phone number unless the number already starts with it. In JSON, send it as a string ("91"), not a number. If you send a number, every phone number in the list is treated as a group name (is_group: true), and those messages fail.msg_typeintegeroptional0text (default),1image,2file or document.msgstringRequired when msg_type is 0Message text. For images and files it's the caption and can be empty. WhatsApp formatting works:
*bold*,_italic_,~strikethrough~.messageis accepted as an alias.
Images and files
img_urlstringRequired when msg_type is 1Public
httporhttpsURL of the image.file_urlstringRequired when msg_type is 2Public
httporhttpsURL the file can be downloaded from directly.file_namestringoptionalFile name recipients see, such as
price-list.pdf. It's sent in lower case, characters such as& : ? * $ ;are replaced with_, and it's cut to 150 characters. If you leave it out, the name comes from the URL.
Delivery options
webhookstringoptionalURL that receives a
POSTfor each message when it's sent or fails. The payload is the same as for Send message.
curl -X POST https://wbiztool.com/api/v1/send_msg/multi/ \
-H "Content-Type: application/json" \
-d '{
"client_id": 12345,
"api_key": "YOUR_API_KEY",
"whatsapp_client": 678,
"msg_type": 1,
"country_code": "91",
"phone": "9876543210,9812345670",
"img_url": "https://example.com/offers/diwali-sale.jpg",
"msg": "Our Diwali sale starts tomorrow."
}'How recipients are read#
Wbiztool splits phone on commas, trims spaces around each item and then decides what each item is:
- Only digits (a leading
+or leading zeros are fine): treated as a phone number.country_codeis added unless the number already starts with it, then the number must be 6 to 15 digits long. - Anything else: treated as a WhatsApp group name, found the same way as in Send to group.
- A group whose name is only digits (for example
2024) is treated as a phone number, and group names containing commas can't be sent from this endpoint. Use Send to group for those.
Other things to know:
- Numbers that are too short or too long after adding the country code are skipped silently. They don't appear in the response and don't get a
msg_id. - Duplicates aren't removed. A number listed twice gets two messages.
- If a local number happens to start with the same digits as
country_code(for example9123456780withcountry_code91), the code isn't added. Send such numbers with the country code already included (919123456780). - Image and file URLs aren't checked when you call the API. They're downloaded when each message is sent, so a broken link makes the messages fail later rather than the request. The same send-time rules as Send message apply: images over 16 MB and videos over 64 MB fail, WAV and OGG audio isn't supported, and files (
msg_type2) without a supported extension get.pdfadded. See Sending images and files.
Credits#
The whole batch is checked against your remaining credits before anything is created. Every non-empty item in phone counts, including items that are later skipped. If the count is higher than your remaining credits, no messages are created and you get:
{
"message": "Not enough credits: 120 messages requested, 85 credits remaining",
"status": 0
}
Messages that are queued but not yet sent also count against your remaining credits. Split large lists into smaller requests or top up your plan. For big campaigns, upload a spreadsheet from the Campaigns page instead.
Response#
A successful request returns HTTP 200:
{
"msg_ids": [9817263, 9817264, 9817265],
"messages": [
{ "msg_id": 9817263, "contact": "919876543210", "is_group": false },
{ "msg_id": 9817264, "contact": "919812345670", "is_group": false },
{ "msg_id": 9817265, "contact": "Sales Team Mumbai", "is_group": true }
],
"message": "Successfully created 3 messages",
"status": 1
}
| Field | Type | Description |
|---|---|---|
status | integer | 1 if at least one message was queued, 0 otherwise. |
message | string | Successfully created N messages on success, otherwise the error. |
msg_ids | array of integers | IDs of the queued messages, in the order of phone. Only present on success. |
messages | array | One object per queued message. Only present on success. |
messages[].msg_id | integer | ID of the message. |
messages[].contact | string | The phone number with the country code applied, or the group name. |
messages[].is_group | boolean | true if the item was treated as a group name. |
Compare messages with the list you sent to find skipped numbers, and check that is_group is false for every item you meant as a phone number.
Errors#
Most errors return HTTP 200 with status set to 0, so always check status in the body. Except for the HTTP 400 and 403 errors, responses (successful ones included) are JSON sent with Content-Type: text/html, so parse the body yourself instead of relying on automatic JSON detection (for example in no-code tools):
{ "message": "Invalid whatsapp client", "status": 0 }
| Message | How to fix it |
|---|---|
Auth Error | Send both client_id and api_key. |
Invalid Client Id | Send client_id as a number. Returned with HTTP 403. |
Auth Error: invalid api key | Check the key exists, hasn't been deleted and belongs to this client_id. Returned with HTTP 400. |
Msg cant be null | Text messages (msg_type 0) need msg. |
Image Url Can't be null | For msg_type 1, send img_url. |
File Url Can't be null | For msg_type 2, send file_url. |
Not enough credits: … messages requested, … credits remaining | Send fewer recipients or add credits. See Credits. |
Invalid whatsapp client | That whatsapp_client ID isn't in your workspace. |
No valid contacts found | Every item in phone was empty or skipped. Check the numbers are 6 to 15 digits with the country code. |
Demo Account can not access apis | Use a regular account. |
Invalid JSON format: … | The JSON body isn't valid, or you sent form fields without client_id. |
Tips#
- Send
phoneas a string: join your list with commas. A JSON array returns{}. - Don't use the Python client's
send_bulk_messagesfor now: it sends the list asphones, which this endpoint ignores. Call the endpoint directly as in the examples above. - Track each message: save every
msg_idfrommessages, or pass awebhookto be notified as each one is sent or fails. - Keep your number connected: every message is sent from your WhatsApp number, so it must stay connected in WhatsApp settings until the whole batch has gone out.
- Different text per person: this endpoint sends the same
msgto everyone. Call Send message once per recipient to personalise each message.
