WhatsApp и Google Таблицы: уведомления по строкам и журнал входящих
Обновлено
Многие небольшие бизнесы ведут записи и заказы в Google Таблице: салон — список клиентов на неделю, магазин — заказы на доставку, школа — оплаты за месяц. Таблицу можно научить писать клиентам в WhatsApp: встроенный в неё Apps Script отправляет запросы к API wapi.kz и записывает результат в соседнюю колонку.
Страница для тех, кто хочет обойтись без CRM и серверов: администраторов, владельцев салонов и магазинов, маркетологов. Код ниже копируется в редактор скриптов таблицы как есть.
- Строка в таблице
- Apps Script по таймеру
- UrlFetchApp: POST /messages/send
- клиент в WhatsApp
- Клиент пишет в WhatsApp
- Apps Script по таймеру
- GET /messages?direction=in
- строка на листе «Входящие»
Что можно автоматизировать
- Напоминание о записи. Каждый вечер скрипт находит записи на завтра и отправляет клиентам время и адрес — подробнее в гайде о напоминаниях.
- Заказ готов. Администратор ставит в колонке «Статус» значение «Готов» и нажимает пункт меню — клиент получает сообщение.
- Оплата за месяц. Список учеников с неоплаченным месяцем — каждому вежливое напоминание с суммой.
- Журнал входящих. Все сообщения клиентов раз в несколько минут попадают на лист «Входящие»: дата, телефон, текст.
- Проверка номеров. Перед отправкой колонка «Есть WhatsApp» заполняется через
POST /contacts/check— до 200 проверок в сутки на номер. - Статусы доставки. По id отправленного сообщения статус можно подтянуть запросом
GET /messages/{id}— доставлено или прочитано (в примере ниже этого нет, добавляется по тому же принципу).
Как это устроено
Со стороны Google: Apps Script с функцией UrlFetchApp.fetch отправляет HTTP-запросы, триггеры по времени запускают функции по расписанию, ключ хранится в свойствах скрипта.
Со стороны wapi.kz — POST /messages/send для отправки (документация) и GET /messages для чтения истории (документация).
Почему журнал входящих сделан опросом, а не вебхуком. Принять вебхук в таблицу можно через веб-приложение Apps Script с функцией doPost(e), но оно отвечает перенаправлением 302. Скрипт при этом выполняется, а мы перенаправления не выполняем — для нас такой ответ выглядит как сбой. Поэтому для таблицы надёжнее опрос истории раз в 5 минут. Если входящие нужны мгновенно, принимайте вебхук через Make, n8n или свой сервер и пишите в таблицу оттуда.
Настройка по шагам
- Подключите номер в wapi.kz (как подключить) и выпустите ключ номера — с ним не нужно поле
from. - Создайте лист «Записи» с колонками: Телефон, Имя, Дата, Время, Статус. И пустой лист «Входящие».
- Откройте Расширения → Apps Script и вставьте код ниже.
- В настройках проекта добавьте свойство скрипта
WAPI_KEYсо значением ключа. - Запустите
sendRemindersвручную и разрешите доступ к таблице и внешним запросам. - В разделе триггеров добавьте:
sendReminders— ежедневно вечером,pullIncoming— каждые 5 минут.
Пример кода
const API = "https://wapi.kz/api/v1";
const key = () => PropertiesService.getScriptProperties().getProperty("WAPI_KEY");
function onOpen() {
SpreadsheetApp.getUi().createMenu("WhatsApp").addItem("Отправить напоминания", "sendReminders").addToUi();
}
// Напоминания: лист «Записи», колонки A–E: Телефон, Имя, Дата, Время, Статус
function sendReminders() {
const sheet = SpreadsheetApp.getActive().getSheetByName("Записи");
const range = sheet.getDataRange();
const values = range.getValues();
const shown = range.getDisplayValues();
const tz = SpreadsheetApp.getActive().getSpreadsheetTimeZone();
const tomorrow = Utilities.formatDate(new Date(Date.now() + 864e5), tz, "yyyy-MM-dd");
for (let i = 1; i < values.length; i++) {
const date = values[i][2];
const [phone, name, , time, status] = shown[i];
if (status || !phone || !(date instanceof Date)) continue;
if (Utilities.formatDate(date, tz, "yyyy-MM-dd") !== tomorrow) continue;
let to = phone.replace(/\D/g, "");
if (to.length === 11 && to[0] === "8") to = "7" + to.slice(1);
const res = UrlFetchApp.fetch(API + "/messages/send", {
method: "post",
contentType: "application/json",
headers: { Authorization: "Bearer " + key() },
payload: JSON.stringify({
to,
text: `${name}, напоминаем о записи завтра в ${time}. Если планы изменились, ответьте на это сообщение.`,
}),
muteHttpExceptions: true,
});
const ok = res.getResponseCode() === 200;
sheet.getRange(i + 1, 5).setValue(ok ? "отправлено" : JSON.parse(res.getContentText()).code);
Utilities.sleep(1100); // не чаще 1 сообщения в секунду с номера
}
}
// Журнал входящих: новые сообщения клиентов на лист «Входящие»
function pullIncoming() {
const props = PropertiesService.getScriptProperties();
const lastId = Number(props.getProperty("LAST_ID") || 0);
const res = UrlFetchApp.fetch(API + "/messages?direction=in&limit=500", {
headers: { Authorization: "Bearer " + key() },
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 200) return;
const fresh = JSON.parse(res.getContentText()).messages
.filter((m) => m.id > lastId && m.peer)
.reverse(); // API отдаёт новые сверху
if (!fresh.length) return;
const sheet = SpreadsheetApp.getActive().getSheetByName("Входящие");
const rows = fresh.map((m) => [m.created_at, m.peer, m.text || "[" + m.type + "]"]);
sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, 3).setValues(rows);
props.setProperty("LAST_ID", String(fresh[fresh.length - 1].id));
}Телефон в колонке A можно писать в любом виде — с плюсом, пробелами или восьмёркой, скрипт приведёт его к 77011234567. При первом запуске pullIncoming заберёт до 500 последних входящих, дальше — только новые. Время в журнале — UTC, как в API.
Ограничения
- Это не встроенная функция Google: готового дополнения wapi.kz для Таблиц нет, работает ваш скрипт.
- Вебхук напрямую в веб-приложение Apps Script не подходит из-за перенаправления 302 — используйте опрос или посредника.
- У Apps Script есть квоты: время одного запуска ограничено (около 6 минут) и есть суточный лимит внешних запросов. Для сотен строк за раз скрипт не подходит.
- Лимиты номера: 1 сообщение в секунду, отдельный суточный потолок первых сообщений новым людям, прогрев в первые 2 недели (лимиты). Таблица с купленной базой — путь к блокировке, пишите только своим клиентам.
- Файл в таблицу не попадает — скрипт пишет тип сообщения, например
[image]. Сам файл хранится у нас и доступен через API поmedia.id:GET /media/{id}отдаёт его по свежей ссылке.
Хотите, чтобы скрипт настроили за вас? Зарегистрируйтесь, подключите номер и оставьте заявку из кабинета. Номер стоит 9 900 ₸ в месяц, без платы за сообщения (цены).
Частые вопросы
Нужен ли сервер или платный Google Workspace?
Нет. Скрипт работает внутри таблицы на обычном Google-аккаунте. Платный Workspace даёт более высокие квоты, но для напоминаний на десятки клиентов в день хватит и бесплатного.
Почему не принимать вебхук прямо в таблицу?
Веб-приложение Apps Script отвечает перенаправлением 302, а мы перенаправления не выполняем. Скрипт может отработать, но надёжности нет. Опрос раз в 5 минут или посредник вроде Make — надёжнее.
Можно ли отправлять сообщение при изменении ячейки?
Можно через устанавливаемый триггер на изменение, но легко отправить лишнее при случайной правке. Пункт меню «Отправить» или проверка по расписанию безопаснее.
Что будет, если запустить скрипт дважды?
Строки с заполненным статусом пропускаются, поэтому повторный запуск не отправит напоминание второй раз.
Как это использовать в салоне или клинике?
Сценарии по отраслям — на страницах салоны красоты и клиники.
Подключите WhatsApp к Google Sheets
9 900 ₸ в месяц за номер, без платы за сообщения. Сервер в Казахстане, оплата Kaspi или счётом на ТОО/ИП. Подключение — около 5 минут.