Отправка сообщений через WhatsApp API: примеры на curl, Node.js, Python и PHP
Как отправить сообщение в WhatsApp из своей программы через REST API wapi.kz: один и тот же запрос на curl, Node.js, Python и PHP, разбор ответа, выбор номера, проверка получателя и обработка ошибок — чтобы интеграция не падала на первом же лимите.
API — обычный HTTP: JSON в теле, ключ в заголовке, SDK не нужен. Если номер ещё не подключён, начните с гайда как подключить WhatsApp API — это около 5 минут.
Первое сообщение за четыре шага
Шаг 1. Возьмите API-ключ
Ключ лежит в личном кабинете, в разделе «API и MCP». Его передают в заголовке каждого запроса:
Authorization: Bearer <API_KEY>Подойдёт и заголовок X-Api-Key: <API_KEY>. Без ключа API ответит 401 с кодом unauthorized, с неверным ключом — invalid_key.
Ключ аккаунта работает со всеми номерами. Подрядчику лучше выпустить ключ номера: он работает только с одним номером и не видит баланс.
Шаг 2. Узнайте id номера
Если номер на аккаунте один, шаг можно пропустить. Если несколько — возьмите id нужного из GET /numbers: отправлять можно с номера, у которого connected: true.
Шаг 3. Проверьте получателя
Перед первым сообщением новому человеку убедитесь, что его номер есть в WhatsApp, — об этом раздел про проверку номера ниже.
Шаг 4. Отправьте сообщение
curl -X POST https://wapi.kz/api/v1/messages/send \
-H "Authorization: Bearer $WAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"77011234567","text":"Здравствуйте! Ваш заказ №1042 готов к выдаче.","from":1}'Ответ при успехе:
{
"ok": true,
"id": 1842,
"wa_message_id": "3EB0A1F2C4D5E6F7",
"from": { "id": 1, "phone": "77010000000" }
}id — номер сообщения в журнале wapi.kz: по нему GET /messages/1842 покажет статус доставки. wa_message_id — id в самом WhatsApp, с ним приходят статусы в вебхуке message.ack. from — номер, с которого ушло сообщение.
Поля запроса
| Поле | Обязательно | Что передавать |
|---|---|---|
to | да | Номер получателя: 10–15 цифр с кодом страны, для Казахстана начинается с 7 |
text | да | Текст до 4096 символов |
from | нет | id вашего номера или его телефон |
Отправка идёт только в личные чаты, не в группы. Пробелы, скобки и плюс в to сервер отбросит сам, а восьмёрку на семёрку не заменит: 87011234567 приводите к виду 77011234567 у себя (см. E.164).
Фото, документы, голосовые и видео отправляются тем же запросом с полями image, file, voice и video — подробно в гайде как отправить фото и файлы.
Выбор номера: поле from
from принимает id номера (1) или его телефон ("77010000000"). Если поле не указать:
- подключён один номер — сообщение уйдёт с него;
- подключено несколько — ответ
400с кодомfrom_required: сервер не угадывает за вас.
Поэтому передавайте from всегда, даже если номер пока один: добавите второй — ничего не сломается. Другие ошибки: number_not_found (такого номера нет в аккаунте) и invalid_from (не id и не телефон). С ключом номера from не нужен. Подробнее — в документации.
Примеры на Node.js, Python и PHP
Все три функции отправляют текст и возвращают ответ, а при ошибке бросают исключение с code — по нему программа решает, что делать дальше.
Node.js
В Node.js 18 и новее fetch встроен, пакеты не нужны. Пример написан как ES-модуль (файл .mjs).
const API = "https://wapi.kz/api/v1";
export async function sendWhatsApp(to, text, from) {
const res = await fetch(`${API}/messages/send`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ to, text, from }), // from: undefined в JSON не попадёт
signal: AbortSignal.timeout(15000),
});
const data = await res.json();
if (!res.ok) {
throw Object.assign(new Error(`${data.code}: ${data.error}`), { code: data.code, status: res.status });
}
return data; // { ok, id, wa_message_id, from: { id, phone } }
}
const msg = await sendWhatsApp("77011234567", "Заказ №1042 готов к выдаче", 1);
console.log("id в журнале:", msg.id);Python
Нужна библиотека requests (pip install requests). Слово from в Python зарезервировано, поэтому аргумент функции назван иначе, а в JSON уходит именно from.
import os
import requests
API = "https://wapi.kz/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['WAPI_KEY']}"}
def send_whatsapp(to, text, from_number=None):
body = {"to": to, "text": text}
if from_number is not None:
body["from"] = from_number
r = requests.post(f"{API}/messages/send", headers=HEADERS, json=body, timeout=15)
data = r.json()
if r.status_code != 200:
raise RuntimeError(data.get("code"), data.get("error")) # code — в e.args[0]
return data
msg = send_whatsapp("77011234567", "Заказ №1042 готов к выдаче", from_number=1)
print("id в журнале:", msg["id"], "wa_message_id:", msg["wa_message_id"])PHP
Подойдёт PHP 7.4 и новее со встроенным cURL — библиотеки не нужны.
<?php
function wapi_send(string $to, string $text, ?int $from = null): array
{
$body = ["to" => $to, "text" => $text];
if ($from !== null) {
$body["from"] = $from;
}
$ch = curl_init("https://wapi.kz/api/v1/messages/send");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("WAPI_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($body, JSON_UNESCAPED_UNICODE),
CURLOPT_TIMEOUT => 15,
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$data = json_decode((string) $raw, true) ?? [];
if ($status !== 200) {
throw new RuntimeException(($data["code"] ?? "network_error") . ": " . ($data["error"] ?? "нет ответа"));
}
return $data;
}
$msg = wapi_send("77011234567", "Заказ №1042 готов к выдаче", 1);
echo "id в журнале: " . $msg["id"] . PHP_EOL;Проверка номера перед первой отправкой
Сообщение на номер без WhatsApp не дойдёт, а ошибки доставки снижают индекс доверия номера. Перед первым сообщением новому клиенту проверьте его:
curl -X POST https://wapi.kz/api/v1/contacts/check \
-H "Authorization: Bearer $WAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"77011234567","from":1}'В ответе exists: true или false, а также chat_id и номер, с которого проверяли. exists: false — номера в WhatsApp нет, отправлять бессмысленно. Лимит — 200 проверок в сутки на номер, не чаще одной в секунду, поэтому сохраняйте результат у себя, а не проверяйте клиента перед каждым сообщением. Детали — в документации.
Обработка ошибок по code
Ошибки приходят в одном виде: {"error": "текст по-русски", "code": "машинный_код"}. Текст пишите в лог, а ветвитесь по code.
| HTTP | code | Что делать |
|---|---|---|
| 400 | invalid_to, empty_text, text_too_long | Исправить данные — повтор с теми же данными не поможет |
| 400 | from_required, invalid_from | Передать корректный from |
| 401 | unauthorized, invalid_key | Проверить ключ: не перевыпущен ли он |
| 402 | number_paused | Номер на паузе — пополнить баланс, номер включится сам |
| 409 | not_connected | Номер отключился от WhatsApp — переподключить в кабинете |
| 429 | rate_limited | Подождать пару секунд и повторить |
| 429 | daily_limit, new_contacts_limit | Не повторять: лимит обновится в полночь по Алматы |
| 429 | cold_blocked, spam_detected | Не повторять: первые сообщения новым людям закрыты на 24 часа, отвечать можно |
| 502 | send_failed | WhatsApp не принял сообщение — повторить позже, проверить номер получателя |
| 503 | capacity | Номер перезапускается — повторить через пару минут |
Главное: на 429 не отвечайте мгновенным повтором в цикле. Для rate_limited хватит короткой паузы, остальные 429 повтором не лечатся — сообщение нужно отложить. На 502 и 503 — 2–3 попытки с паузой в минуты. Если сообщение успело попасть в журнал, в ответе с ошибкой тоже будет id.
Все коды — в документации, разбор поломок — в гайде WhatsApp API не работает.
Очередь: не чаще одного сообщения в секунду
С одного номера — не чаще 1 сообщения в секунду, первые сообщения новым людям — не чаще раза в 5 секунд. Это правила wapi.kz, они снижают риск блокировки (лимиты). Пачка без пауз получит rate_limited.
Решение — своя очередь у каждого номера. Набросок на Node.js с sendWhatsApp из примера выше:
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const queues = new Map(); // ключ — id номера
export function enqueue(from, to, text) {
if (!queues.has(from)) queues.set(from, { jobs: [], running: false });
const q = queues.get(from);
q.jobs.push({ to, text });
if (!q.running) drain(from, q);
}
async function drain(from, q) {
q.running = true;
while (q.jobs.length) {
const job = q.jobs.shift();
try {
await sendWhatsApp(job.to, job.text, from);
} catch (e) {
if (e.code === "rate_limited") q.jobs.unshift(job); // вернуть в начало и подождать
else if (e.code === "send_failed" || e.code === "capacity") console.warn("Отложить и повторить позже:", job.to, e.code);
else console.error("Не отправлено:", job.to, e.code, e.message);
}
await sleep(1100);
}
q.running = false;
}Очередь живёт в памяти одного процесса. Если отправляют несколько серверов или задания нельзя терять при перезапуске, храните их в базе и отправляйте одним воркером на номер.
Есть и суточные лимиты: на уровне доверия «Хороший» — 1 000 сообщений и 150 первых сообщений новым людям, в первые две недели — меньше (прогрев). Текущие цифры — GET /limits?from=1.
Где хранить ключ
Ключ аккаунта позволяет писать с любого вашего номера и читать переписку — обращайтесь с ним как с паролем:
- храните его в переменной окружения или менеджере секретов, а не в коде и не в git;
- не вызывайте API из браузера или мобильного приложения — ключ увидит любой; отправляйте через свой сервер;
- подрядчикам и отдельным интеграциям выдавайте ключи номеров — каждый отзывается отдельно;
- если ключ утёк, перевыпустите его в кабинете: старый перестанет работать сразу.
Перевыпуск ключа отключит все интеграции, где он прописан, — держите их список под рукой.
Получить ключ можно сразу после регистрации и подключения номера. Номер стоит 9 900 ₸ в месяц, платы за сообщения, запросы к API и вебхуки нет — подробности на странице цен.
Частые вопросы
Как получить API-ключ WhatsApp?
Зарегистрируйтесь на wapi.kz, пополните баланс и подключите номер по QR-коду или коду по номеру — ключ появится в кабинете в разделе «API и MCP». Бесплатного теста нет, минимальное пополнение — 1 000 ₸.
Нужен ли SDK или библиотека?
Нет. Это обычный REST API поверх HTTP: подойдёт любой язык, который умеет отправлять POST с JSON. SDK у wapi.kz нет.
Можно ли отправить сообщение в группу?
Нет, только в личные чаты. Сообщения из групп, где состоит ваш номер, приходят в вебхук и видны в истории.
Ответ 200, а клиент сообщения не видит — почему?
Ответ 200 значит, что сообщение ушло в WhatsApp. Дальше смотрите статус: GET /messages/{id} или вебхук message.ack. Если статус долго остаётся sent, получатель не в сети или номер набран неверно.
Можно ли отправить рассылку по своей базе?
Массовые сообщения незнакомым мы не поддерживаем: это главная причина блокировок WhatsApp, и защита от рассылок закроет первые сообщения новым людям на 24 часа. API — для сообщений, которых клиент ждёт: заказы, записи, ответы на заявки.
Подключите WhatsApp к своим системам
9 900 ₸ в месяц за номер, без платы за сообщения. Сервер в Казахстане, оплата Kaspi или счётом на ТОО/ИП. Подключение — около 5 минут.