Интеграции

WhatsApp для сайта: заявки с формы — в WhatsApp менеджеру и клиенту

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

Заявка, которая до вечера лежит в почте, часто уже никому не нужна. Если отправлять её в WhatsApp, менеджер увидит её на телефоне сразу, а клиент получит подтверждение с вашего рабочего номера — и сможет ответить в тот же чат.

Разберём, как подключить WhatsApp API к сайту: схему, согласие клиента, формат телефона, защиту формы от ботов и готовые обработчики на PHP, Node.js и Python.

Схема: форма → ваш сервер → WhatsApp

  1. Посетитель заполняет форму: имя, телефон, вопрос, галочка согласия.
  2. Браузер отправляет форму на ваш сервер — PHP-скрипт на хостинге, Node.js или Python.
  3. Сервер проверяет данные и вызывает POST /messages/send дважды: менеджеру — текст заявки, клиенту — подтверждение.
  4. Оба сообщения уходят с вашего номера, подключённого к wapi.kz. Ответ клиента придёт на тот же номер, на телефон менеджера.

Отправка с сайта в WhatsApp идёт только через сервер, не из браузера. Параметры запроса — в документации.

Менеджеру пишите на его личный WhatsApp — заявка придёт с обычным уведомлением. Если менеджеров несколько, ведите заявки в CRM — см. WhatsApp и CRM.

Сценарии

  • Заявка с формы. Менеджеру: имя, телефон, вопрос, страница. Клиенту: «Здравствуйте, Айгерим! Получили вашу заявку, менеджер свяжется в рабочее время». Не обещайте в тексте сроков, которые не соблюдаете.
  • Заказ в интернет-магазине. Клиенту: номер заказа, сумма, состав. Менеджеру: новый заказ и способ доставки. Для готовых платформ — страницы Tilda и WooCommerce, сценарии — в разделе для интернет-магазинов.
  • Запись на услугу. Подтверждение сразу и напоминание накануне — см. напоминания о записи.

Кнопке «Написать в WhatsApp» со ссылкой wa.me API не нужен: там диалог начинает клиент. API нужен, когда пишет ваша система.

Добавьте в форму галочку «Согласен получать сообщения в WhatsApp по этой заявке». Она не должна стоять заранее. Подтверждение отправляйте, только если галочка отмечена, а сам факт согласия сохраняйте вместе с заявкой: время, страницу, текст галочки.

Имя и телефон — персональные данные. Как их собирать и где хранить, регулирует Закон РК «О персональных данных и их защите». Это не юридическая консультация, уточните у юриста; основные моменты — в гайде о хранении персональных данных в Казахстане.

Телефон: приводим к формату API

API ждёт номер цифрами с кодом страны: 77011234567. Посетители вводят «8 701 123 45 67», «+7 (701)…» или «701…». Номер с «8» в начале API отправит не туда, куда нужно, поэтому на сервере:

  • оставьте только цифры;
  • 11 цифр с «8» в начале — замените «8» на «7»;
  • 10 цифр — допишите «7» спереди;
  • всё, что короче 11 или длиннее 15 цифр, — ошибка, просите исправить.

Маска ввода в форме не заменяет проверку на сервере. Есть ли у номера WhatsApp, можно проверить через POST /contacts/check — до 200 проверок в сутки на номер.

Защита формы от спама

Боты заполняют формы случайными номерами, и без защиты ваш номер начнёт писать незнакомым людям: сгорит суточный лимит первых сообщений новым людям, вырастут ошибки доставки, упадёт индекс доверия. Если за час появится 40+ новых контактов, первые сообщения новым людям остановятся на 24 часа — и настоящие клиенты подтверждений не получат.

  • Honeypot. Скрытое поле, например website, которое человек не видит. Заполнено — это бот: отвечайте «ок» и ничего не отправляйте.
  • Лимит по IP. Например, не больше 3 заявок с одного адреса за 10 минут.
  • Лимит по телефону. Одно подтверждение на номер в сутки, повторы — только менеджеру.
  • Капча — если атака всё-таки идёт.

Учитывайте лимиты номера. На уровне «Хороший» — 150 первых сообщений новым людям в сутки, а в первые три дня после подключения — 20. Первые сообщения новым людям — не чаще раза в 5 секунд. Подробно — в разделе лимитов и в гайде как снизить риск блокировки.

Ключ — только на сервере

Никогда не вставляйте API-ключ в JavaScript страницы. Любой посетитель откроет инструменты разработчика, скопирует ключ и сможет писать с вашего номера.

Храните ключ в переменной окружения или в файле конфигурации вне публичной папки сайта. Для сайта выпустите отдельный ключ номера: он работает только с одним номером и не видит баланс. Если ключ утёк — отзовите его в карточке номера, остальные интеграции продолжат работать.

Обработчики на PHP, Node.js и Python

Форма отправляет поля name, phone, message, consent (галочка) и website (honeypot). Между двумя сообщениями — пауза из-за лимита 1 сообщение в секунду.

PHP: обычный хостинг

Для WhatsApp API на PHP хватает встроенного cURL, библиотеки не нужны. Нужен PHP 7.4 или новее.

PHP
<?php
// lead.php — заявка с формы → WhatsApp менеджеру и клиенту
const MANAGER_PHONE = "77010000001";

function wapi_send(string $to, string $text): bool {
    $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(["to" => $to, "text" => $text], JSON_UNESCAPED_UNICODE),
        CURLOPT_TIMEOUT => 15,
    ]);
    $data = json_decode((string) curl_exec($ch), true);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($code !== 200) error_log("wapi: " . ($data["code"] ?? $code) . " — " . ($data["error"] ?? ""));
    return $code === 200;
}

function normalize_phone(string $raw): ?string {
    $d = preg_replace('/\D/', '', $raw);
    if (strlen($d) === 11 && $d[0] === "8") $d = "7" . substr($d, 1);
    if (strlen($d) === 10) $d = "7" . $d;
    return (strlen($d) >= 11 && strlen($d) <= 15) ? $d : null;
}

if ($_SERVER["REQUEST_METHOD"] !== "POST") { http_response_code(405); exit; }
if (!empty($_POST["website"])) exit("ok");                 // honeypot: бот

$file = sys_get_temp_dir() . "/lead_" . md5($_SERVER["REMOTE_ADDR"]);
$hits = json_decode(@file_get_contents($file) ?: "[]", true) ?: [];
$hits = array_filter($hits, fn($t) => $t > time() - 600);
if (count($hits) >= 3) { http_response_code(429); exit("Слишком много заявок, попробуйте позже"); }
$hits[] = time();
file_put_contents($file, json_encode(array_values($hits)));

$name  = mb_substr(trim($_POST["name"] ?? ""), 0, 100);
$phone = normalize_phone($_POST["phone"] ?? "");
$msg   = mb_substr(trim($_POST["message"] ?? ""), 0, 1000);
if ($name === "" || $phone === null) { http_response_code(400); exit("Проверьте имя и телефон"); }

wapi_send(MANAGER_PHONE, "Заявка с сайта\nИмя: $name\nТелефон: +$phone\nВопрос: $msg");
if (!empty($_POST["consent"])) {
    sleep(2);                                                // не чаще 1 сообщения в секунду
    wapi_send($phone, "Здравствуйте, $name! Получили вашу заявку, менеджер свяжется в рабочее время.");
}
echo "ok";

Node.js (Express)

JavaScript
import express from "express";

const app = express();
const hits = new Map();
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const normalizePhone = (raw) => {
  let d = String(raw || "").replace(/\D/g, "");
  if (d.length === 11 && d.startsWith("8")) d = "7" + d.slice(1);
  if (d.length === 10) d = "7" + d;
  return d.length >= 11 && d.length <= 15 ? d : null;
};

async function send(to, text) {
  try {
    const res = await fetch("https://wapi.kz/api/v1/messages/send", {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.WAPI_KEY}`, "Content-Type": "application/json" },
      body: JSON.stringify({ to, text }),
    });
    if (!res.ok) console.error("wapi:", res.status, await res.text());
  } catch (err) {
    console.error("wapi:", err.message);
  }
}

app.post("/lead", express.urlencoded({ extended: false }), async (req, res) => {
  const { name = "", phone: raw, message = "", consent, website } = req.body;
  if (website) return res.send("ok");                       // honeypot
  const recent = (hits.get(req.ip) || []).filter((t) => t > Date.now() - 600_000);
  if (recent.length >= 3) return res.status(429).send("Слишком много заявок");
  hits.set(req.ip, [...recent, Date.now()]);

  const phone = normalizePhone(raw);
  if (!name.trim() || !phone) return res.status(400).send("Проверьте имя и телефон");
  res.send("ok");                                           // не держим посетителя

  await send(process.env.MANAGER_PHONE, `Заявка с сайта\nИмя: ${name}\nТелефон: +${phone}\nВопрос: ${message}`);
  if (consent) {
    await sleep(2000);
    await send(phone, `Здравствуйте, ${name}! Получили вашу заявку, менеджер свяжется в рабочее время.`);
  }
});

app.listen(3000);

За прокси включите app.set("trust proxy", 1), иначе req.ip — адрес прокси.

Python (Flask)

Python
import os, re, time
import requests
from flask import Flask, request

app = Flask(__name__)
H = {"Authorization": f"Bearer {os.environ['WAPI_KEY']}"}
hits = {}


def normalize_phone(raw):
    d = re.sub(r"\D", "", raw or "")
    if len(d) == 11 and d.startswith("8"):
        d = "7" + d[1:]
    if len(d) == 10:
        d = "7" + d
    return d if 11 <= len(d) <= 15 else None


def send(to, text):
    try:
        r = requests.post("https://wapi.kz/api/v1/messages/send", headers=H,
                          json={"to": to, "text": text}, timeout=15)
        if r.status_code != 200:
            app.logger.error("wapi: %s %s", r.status_code, r.text[:300])
    except requests.RequestException as e:
        app.logger.error("wapi: %s", e)


@app.post("/lead")
def lead():
    f = request.form
    if f.get("website"):
        return "ok"  # honeypot
    ip = request.remote_addr
    recent = [t for t in hits.get(ip, []) if t > time.time() - 600]
    if len(recent) >= 3:
        return "Слишком много заявок", 429
    hits[ip] = recent + [time.time()]

    name, phone = f.get("name", "").strip(), normalize_phone(f.get("phone"))
    if not name or not phone:
        return "Проверьте имя и телефон", 400
    send(os.environ["MANAGER_PHONE"], f"Заявка с сайта\nИмя: {name}\nТелефон: +{phone}\nВопрос: {f.get('message', '')}")
    if f.get("consent"):
        time.sleep(2)
        send(phone, f"Здравствуйте, {name}! Получили вашу заявку, менеджер свяжется в рабочее время.")
    return "ok"

Две одновременные заявки могут получить rate_limited (429). При большом потоке складывайте заявки в очередь и отправляйте отдельным процессом.

Частые вопросы

Можно ли отправлять WhatsApp прямо из JavaScript на странице?

Нет. Ключ окажется в коде страницы, и любой сможет писать с вашего номера. Браузер отправляет форму на ваш сервер, а сервер — запрос в wapi.kz.

Подойдёт ли обычный хостинг на PHP?

Да, если на нём есть cURL и разрешены исходящие https-запросы. Пример выше работает без библиотек.

Что будет, если у клиента нет WhatsApp?

Сообщение не доставится, а ошибки доставки снижают индекс доверия номера. Если таких заявок много, проверяйте номер через POST /contacts/check до подтверждения. Менеджеру заявка уходит в любом случае.

Сколько стоят заявки с сайта в WhatsApp?

Номер — 9 900 ₸ в месяц, платы за сообщения и запросы нет. Ограничение — лимиты номера, а не деньги. Подробнее — на странице цен.

Подтверждение не пришло клиенту — что проверить?

Формат телефона, подключён ли номер в кабинете и код ошибки в логах сервера. Частые причины разобраны в гайде о решении проблем с API.

С чего начать?

Зарегистрируйтесь, подключите номер (около 5 минут), выпустите для сайта ключ номера и проверьте форму на своём телефоне.

Подключите WhatsApp к своим системам

9 900 ₸ в месяц за номер, без платы за сообщения. Сервер в Казахстане, оплата Kaspi или счётом на ТОО/ИП. Подключение — около 5 минут.