Stateless-автоматизація: Розгортання Google Apps Script як Webhook-сервера
Коли в компанії з’являється кілька CRM, форм, платіжних сервісів і внутрішніх таблиць, дуже швидко виростає “зоопарк” інтеграцій. Один сценарій забирає лід із форми, другий надсилає його в таблицю, третій пушить сповіщення менеджеру, четвертий оновлює статус. У no-code це виглядає зручно рівно до моменту, поки ви не починаєте платити за кожен крок, кожен запуск і кожен зайвий роут між сервісами.

У таких місцях Google Apps Script часто закриває задачу простіше. Якщо розгорнути скрипт як Web App, він може приймати HTTP POST-запити через doPost(e) і повертати TextOutput через ContentService. Тобто у вас з’являється легкий webhook-ендпоінт без окремого сервера, а вхідні дані можна одразу писати в Google Sheets DB. Саме це я й називаю stateless-підходом у контексті Apps Script: ми не будуємо складний backend, а обробляємо вхідний payload і відразу передаємо його в таблицю або далі по ланцюжку.
Що саме робить doPost(e)
Механіка тут пряма. Зовнішня система надсилає POST-запит на URL вашого Web App, Apps Script ловить його у doPost(e), а тіло запиту стає доступним у параметрі e. Далі ви або читаєте e.postData.contents, або забираєте query/body-поля, валідовуєте їх і записуєте в потрібний рядок таблиці. Це не “імітація API”, а цілком нормальна безсерверна функція для прийому вебхуків. Google офіційно описує doPost(e) саме як обробник HTTP POST для web app, а відповідь має повертатися як HtmlOutput або ContentService.createTextOutput().
Практична цінність у тому, що таблиця стає живим шаром інтеграції. Лід прийшов із CRM, Typeform або платіжного сервісу — і вже за секунду лежить у Google Sheets. Той самий принцип працює і в сценаріях, де дані прилітають не з форми, а з месенджера — наприклад, через Telegram-бота, який записує аудіо-нотатки в таблицю. Якщо зверху потрібні сповіщення або наступні дії, цей самий сценарій добре поєднується з логікою на кшталт інтеграції Google Sheets з Telegram та Slack або подальшої автоматизації таблиць через Google Apps Script.
Кейс: прийом ліда з зовнішньої CRM або форми

Уявімо простий сценарій. У вас є форма або CRM, яка вміє стріляти webhook після нового ліда. Такий самий підхід можна використовувати і для нетипових джерел даних — наприклад, коли Telegram-бот перетворює голосові нотатки на структуровані записи і відправляє їх у Google Sheets. Payload містить ім’я, email, телефон, джерело й коментар. Замість того щоб ганяти це через ланцюжок із трьох сервісів, ви даєте системі URL вашого Apps Script Web App. Скрипт приймає JSON, перевіряє секретний токен, пише рядок у таблицю і повертає JSON-відповідь success: true.
На рівні Time-to-market це дуже сильний формат. Вам не потрібен VPS, деплой Node.js, nginx, PM2 чи окремий endpoint-monitoring для першої версії. Якщо задача — швидко підняти обробку HTTP-запитів і звести дані в одну таблицю, Apps Script часто виграє за співвідношенням “швидкість запуску / користь”.
Створили: таблицю та Web App
Перший крок — підготувати Google Sheets, куди будуть летіти дані. Далі відкриваєте редактор Apps Script, створюєте файл зі скриптом і пишете doPost(e). Коли скрипт готовий, його потрібно задеплоїти як Web App. Для цього в Apps Script є окремий сценарій деплою, де задається, від чийого імені виконується застосунок і хто має до нього доступ. У web app маніфесті Google використовує режими виконання на кшталт USERDEPLOYING та USERACCESSING, а самі web apps офіційно підтримують доступ рівня “anyone” залежно від налаштування деплою.
Для webhook-сценаріїв зазвичай обирають виконання від імені того, хто деплоїть, і доступ рівня Anyone або Anyone with Google account — залежно від того, хто саме має надсилати запити. Якщо зовнішня CRM або Stripe не авторизується через Google-акаунт, вам потрібен саме публічний endpoint, але тоді токен-перевірка стає обов’язковою, а не “непоганою ідеєю”.
Написали: прийом JSON, валідація і запис у таблицю
Базовий робочий шаблон виглядає так:
function doPost(e) {
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Leads');
const secret = PropertiesService.getScriptProperties().getProperty('WEBHOOK_SECRET');
const raw = e.postData && e.postData.contents ? e.postData.contents : '';
const data = JSON.parse(raw);
if (!data || data.secret !== secret) {
return ContentService
.createTextOutput(JSON.stringify({ success: false, error: 'Unauthorized' }))
.setMimeType(ContentService.MimeType.JSON);
}
if (!data.name || !data.email) {
return ContentService
.createTextOutput(JSON.stringify({ success: false, error: 'Missing required fields' }))
.setMimeType(ContentService.MimeType.JSON);
}
const nextRow = sheet.getLastRow() + 1;
sheet.getRange(nextRow, 1, 1, 5).setValues([[
new Date(),
data.name,
data.email,
data.phone || '',
data.source || ''
]]);
return ContentService
.createTextOutput(JSON.stringify({ success: true, row: nextRow }))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService
.createTextOutput(JSON.stringify({ success: false, error: error.message }))
.setMimeType(ContentService.MimeType.JSON);
}
}Тут важливі три речі. Перша — ми працюємо з e.postData.contents, бо саме там лежить сирий JSON. Друга — використовуємо ContentService.createTextOutput() і MIME type для JSON, щоб сервіс-відправник отримав зрозумілу машині відповідь. Третя — не пишемо дані в таблицю “як є”, поки не перевірили обов’язкові ключі та секрет. ContentService в Apps Script якраз і призначений для таких відповідей: plain text, JSON, XML, CSV та інших текстових форматів. :contentReference[oaicite:3]{index=3}
Деплоїли: публікуємо endpoint
Після збереження коду робите Deploy → New deployment → Web app, задаєте доступ і отримуєте URL. Це і є ваш API-ендпоінт. Важливий нюанс: якщо ви міняєте код, перевіряйте, чи оновлено саме потрібний deployment, а не тільки редакторський стан. У Apps Script можна мати окремі версії деплою, і це зручно, якщо webhook уже використовується у продакшні. :contentReference[oaicite:4]{index=4}
На цьому ж етапі варто одразу винести секретний ключ у Script Properties, а не тримати його в коді. Це дрібниця, але саме з таких дрібниць починається нормальна аутентифікація запитів.
Протестували: через Postman або cURL

Не треба чекати реальний webhook від CRM, щоб зрозуміти, чи все працює. Найшвидший тест — звичайний curl або Postman. Наприклад так:
curl -X POST "YOUR_WEB_APP_URL" \
-H "Content-Type: application/json" \
-d '{
"secret": "YOUR_SECRET",
"name": "Іван",
"email": "[email protected]",
"phone": "+380000000000",
"source": "Typeform"
}'Якщо все налаштовано правильно, ви побачите JSON-відповідь і новий рядок у таблиці. Саме тут зручно ловити 90% помилок: кривий JSON parsing, неправильний sheet name, відсутність доступу до таблиці, порожній postData, зламаний токен або не той deployment URL.
Безпека: без токена публічний webhook швидко перетворюється на смітник

Якщо ви публікуєте web app з доступом для зовнішніх систем, без перевірки секрету endpoint дуже легко заспамити. Мінімальний захист — shared secret у header або в body. Краще — окремий токен, whitelist по полях і базова валідація payload. Для чутливих сценаріїв цього вже мало, але для прийому лідів, форм чи службових повідомлень це нормальний старт.
Ще один практичний момент: Apps Script вимагає авторизації для доступу до сервісів Google на кшталт Sheets, тому права скрипта й OAuth scopes мають бути коректно надані під час першого запуску та деплою. Якщо цього не зробити, webhook technically існує, але запис у таблицю не відбудеться. :contentReference[oaicite:5]{index=5}
Коли цього достатньо, а коли пора на Node.js
Якщо у вас помірний трафік, проста схема JSON → валідація → рядок у таблиці, мінімальна аутентифікація запитів і немає складної бізнес-логіки, Apps Script закриває задачу дуже добре. Особливо коли бізнесу потрібен результат зараз, а не “після нормального бекенду”.
Але межа теж існує. Якщо вам потрібні черги, retry-логіка, складні роутинги між кількома сервісами, контроль конкурентного запису, важке логування, проксі, обробка великих масивів або окремий шар мікросервісів, тоді вже чесніше переходити на обробку даних на Node.js. Apps Script — чудовий швидкий інструмент, але не універсальна заміна повноцінному серверу.
І саме в цьому його сила: ми використовуємо стандартні інструменти Google на 200% їхніх можливостей, коли це економить час і гроші, а не тому, що “так простіше назавжди”.
Останні статті

Автопілот для Gmail: Як навчити пошту саму сортувати важливі листи за допомогою ШІ
Ранок із 50+ листами — це не “організована комунікація”, а міні-лотерея. Більшість повідомлень — шум: розсилки, автоматичні підтвердження, квитанції, другорядні апдейти.…

Прощавай, ручне копіювання: Як за допомогою ШІ зібрати дані з будь-якого сайту за 2 хвилини
Потрібно виписати ціни 50 товарів із сайту конкурента? Зібрати заголовки з блогу? Витягнути список контактів із відкритого каталогу? Робити це руками через нескінченні C…

Ваш особистий Telegram-асистент: Як за 15 хвилин створити бота для нотаток у Google Таблиці
Записувати витрати, ідеї чи короткі службові нотатки прямо в таблицю з телефону — це один із тих дрібних процесів, які майже всі ненавидять. Маленькі клітинки, незручний…

Автономні LLM-агенти в Google Workspace: Автоматична відповідь на Email через Node.js
У відділах продажу й сапорту email дуже швидко перетворюється на окремий фронт операційного навантаження. Листів багато, формулювання різні, частина звернень повторюєтьс…

Мікроавтоматизація без SaaS: створюємо bookmarklet для YouTube, який додає десятки відео в чергу однією кнопкою
Коли треба переглянути 20–50 відео по темі, ручне додавання кожного ролика в YouTube queue починає дратувати набагато сильніше, ніж здається на старті. Начебто це всього…

Голосове керування бізнесом: Створюємо Telegram-бота, який розуміє ваші аудіо-нотатки
Хороші ідеї, витрати й важливі дрібниці рідко приходять у зручний момент. Зазвичай це трапляється за кермом, між зустрічами, у черзі або дорогою на об’єкт. Відкрити нота…