Разработчикам

Отправка сообщений через WhatsApp API: примеры на curl, Node.js, Python и PHP

Обновлено 7 мин чтения

Как отправить сообщение в WhatsApp из своей программы через REST API wapi.kz: один и тот же запрос на curl, Node.js, Python и PHP, разбор ответа, выбор номера, проверка получателя и обработка ошибок — чтобы интеграция не падала на первом же лимите.

API — обычный HTTP: JSON в теле, ключ в заголовке, SDK не нужен. Если номер ещё не подключён, начните с гайда как подключить WhatsApp API — это около 5 минут.

Первое сообщение за четыре шага

Шаг 1. Возьмите API-ключ

Ключ лежит в личном кабинете, в разделе «API и MCP». Его передают в заголовке каждого запроса:

HTTP
Authorization: Bearer <API_KEY>

Подойдёт и заголовок X-Api-Key: <API_KEY>. Без ключа API ответит 401 с кодом unauthorized, с неверным ключом — invalid_key.

Ключ аккаунта работает со всеми номерами. Подрядчику лучше выпустить ключ номера: он работает только с одним номером и не видит баланс.

Шаг 2. Узнайте id номера

Если номер на аккаунте один, шаг можно пропустить. Если несколько — возьмите id нужного из GET /numbers: отправлять можно с номера, у которого connected: true.

Шаг 3. Проверьте получателя

Перед первым сообщением новому человеку убедитесь, что его номер есть в WhatsApp, — об этом раздел про проверку номера ниже.

Шаг 4. Отправьте сообщение

bash
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}'

Ответ при успехе:

JSON
{
  "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).

JavaScript
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.

Python
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
<?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 не дойдёт, а ошибки доставки снижают индекс доверия номера. Перед первым сообщением новому клиенту проверьте его:

bash
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.

HTTPcodeЧто делать
400invalid_to, empty_text, text_too_longИсправить данные — повтор с теми же данными не поможет
400from_required, invalid_fromПередать корректный from
401unauthorized, invalid_keyПроверить ключ: не перевыпущен ли он
402number_pausedНомер на паузе — пополнить баланс, номер включится сам
409not_connectedНомер отключился от WhatsApp — переподключить в кабинете
429rate_limitedПодождать пару секунд и повторить
429daily_limit, new_contacts_limitНе повторять: лимит обновится в полночь по Алматы
429cold_blocked, spam_detectedНе повторять: первые сообщения новым людям закрыты на 24 часа, отвечать можно
502send_failedWhatsApp не принял сообщение — повторить позже, проверить номер получателя
503capacityНомер перезапускается — повторить через пару минут

Главное: на 429 не отвечайте мгновенным повтором в цикле. Для rate_limited хватит короткой паузы, остальные 429 повтором не лечатся — сообщение нужно отложить. На 502 и 503 — 2–3 попытки с паузой в минуты. Если сообщение успело попасть в журнал, в ответе с ошибкой тоже будет id.

Все коды — в документации, разбор поломок — в гайде WhatsApp API не работает.

Очередь: не чаще одного сообщения в секунду

С одного номера — не чаще 1 сообщения в секунду, первые сообщения новым людям — не чаще раза в 5 секунд. Это правила wapi.kz, они снижают риск блокировки (лимиты). Пачка без пауз получит rate_limited.

Решение — своя очередь у каждого номера. Набросок на Node.js с sendWhatsApp из примера выше:

JavaScript
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 минут.