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

Розгортання Google Apps Script як Webhook-сервера
Розгортання Google Apps Script як Webhook-сервера

У таких місцях 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 або форми
Ілюстрація прийому ліда з зовнішньої 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

Postman або cURL
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% їхніх можливостей, коли це економить час і гроші, а не тому, що “так простіше назавжди”.