Юр. сторона и интеграции

Заявки с сайта в таблицу: простая CRM без внедрения CRM

Как складывать лиды с форм в таблицу и мессенджер, не покупая CRM, что при этом с персональными данными и когда пора на нормальную систему.

11 минут чтения 2 Дмитрий Акиндинов
Содержание
  1. Кому таблицы хватает, а кому уже нет
  2. Схема без сторонних сервисов
  3. Что писать в строку
  4. Персональные данные в облачной таблице
  5. Российские альтернативы
  6. Дубли и потери
  7. Когда таблица начинает мешать
  8. Что делать дальше
  9. Частые вопросы

При потоке в 10-40 заявок в месяц таблица плюс уведомление в мессенджер закрывают задачу не хуже CRM и стоят ноль. Важны две вещи: таймаут на запись в 5 секунд, чтобы форма не висла, и российское хранилище, иначе получаете трансграничную передачу данных.

Раз в месяц ко мне приходят с одинаковой задачей: настроить выгрузку заявок в google таблицы, чтобы лиды с формы перестали теряться в почтовом ящике. Следом обычно идёт второй вопрос: может, сразу купить CRM и не мучиться. Отвечаю сразу: при потоке 10-40 заявок в месяц таблица плюс уведомление в мессенджер закрывают почти всю реальную потребность, а стоят один вечер работы вместо внедрения на два месяца. Ниже разберу схему целиком: как это устроено технически, что писать в строку, куда при этом деваются персональные данные и в какой момент таблица начинает мешать продавать.

Форма с сайта отправляет заявку в таблицу и в мессенджер

Я обслуживаю сайты малого бизнеса шесть лет и повидал обе крайности. Одни героически ведут заявки в переписке с самим собой в Telegram. Другие купили CRM за 60 000 в год, завели там три сделки и больше не заходили. Таблица честно живёт посередине и в 2026 году всё ещё остаётся самым недооценённым инструментом.

Кому таблицы хватает, а кому уже нет

Таблица работает, пока выполняются условия. Их немного, и они проверяются за минуту.

  • Заявок до полусотни в месяц. Верхняя граница нежёсткая, но после 60-80 строк в месяц глазами это уже не читается.
  • Обрабатывает один человек, максимум двое. Три менеджера в одной таблице начинают перетирать чужие правки.
  • Цикл сделки короткий: позвонили, договорились, выставили счёт. Если сделка живёт три месяца и проходит семь этапов, таблица превращается в филиал ада.
  • Нет обязательной отчётности по воронке. Владелец смотрит на количество и на источник, а не строит когортный анализ.
  • Повторных продаж мало или они идут не через сайт.

Совпало четыре пункта из пяти: берите таблицу и не тратьте деньги. Не совпало два и больше: сразу смотрите в сторону нормальной системы, потому что переезжать потом всё равно придётся, только с грязными данными.

Отдельно про мотивацию. Владельцы часто хотят CRM не ради воронки, а ради одной простой вещи: чтобы заявка не потерялась. Это решается не системой за 60 000 в год, а дублированием канала доставки. Письмо может уйти в спам, вебхук может отвалиться, менеджер может смахнуть уведомление. Три независимых приёмника одной заявки надёжнее любой CRM с единственным каналом.

Схема без сторонних сервисов

Типовое решение из статей в интернете выглядит так: форма шлёт данные в Zapier или Make, оттуда в таблицу. С 2022 года это плохая идея по двум причинам. Оплата зарубежных сервисов из России — отдельный квест, а данные клиентов ходят через посредника, который вам ничего не обещал.

Схема без посредников проще, чем кажется:

[форма на сайте]
      |
      v
[обработчик на вашем домене: mail.php]
      |
      |--> письмо на рабочую почту
      |--> сообщение в Telegram-бота
      |--> POST в веб-приложение Apps Script -> строка в таблице
      |--> строка в leads.log на сервере (страховка)

Обработчик у вас уже есть, если на сайте вообще работает форма. Задача сводится к тому, чтобы добавить в него один HTTP-запрос и не сломать остальное.

Что делает обработчик

Порядок действий внутри скрипта важен. Сначала пишем в локальный лог, потом рассылаем по каналам. Если упадёт вебхук в таблицу, заявка уже сохранена на диске и её можно долить руками.

// после валидации и проверки honeypot
$lead = [
    'ts'      => date('c'),
    'id'      => bin2hex(random_bytes(8)),   // ключ идемпотентности
    'name'    => $name,
    'phone'   => $phone,
    'email'   => $email,
    'message' => $message,
    'page'    => $_POST['page_url'] ?? '',
    'utm'     => $_POST['utm'] ?? '',
    'ip'      => $_SERVER['REMOTE_ADDR'],
];

// 1. страховка на диске, вне docroot
file_put_contents(
    '/home/c/user/site/private/leads.log',
    json_encode($lead, JSON_UNESCAPED_UNICODE) . "n",
    FILE_APPEND | LOCK_EX
);

// 2. вебхук в таблицу, короткий таймаут
$ch = curl_init(SHEET_WEBHOOK_URL);
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($lead, JSON_UNESCAPED_UNICODE),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 5,
    CURLOPT_FOLLOWLOCATION => true, // Apps Script отвечает 302 на редирект
]);
$resp = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($code !== 200) {
    file_put_contents('/home/c/user/site/private/leads-retry.log',
        json_encode($lead, JSON_UNESCAPED_UNICODE) . "n", FILE_APPEND | LOCK_EX);
}

Таймаут в 5 секунд обязателен. Без него любая задержка на стороне таблицы превращается в подвисшую форму, посетитель жмёт кнопку второй раз, и вы получаете дубль. Про CURLOPT_FOLLOWLOCATION тоже не забудьте: веб-приложения Apps Script отвечают редиректом на googleusercontent, и без этого флага вы будете видеть код 302 и думать, что всё сломано.

Приёмник на стороне таблицы

Скрипт живёт прямо в таблице: Расширения, Apps Script, дальше публикация как веб-приложение с доступом «всем, у кого есть ссылка». Ссылку никому не показываем, она играет роль ключа.

function doPost(e) {
  var lock = LockService.getScriptLock();
  lock.waitLock(20000);                     // защита от гонок при пачке заявок
  try {
    var d = JSON.parse(e.postData.contents);
    var sh = SpreadsheetApp.getActive().getSheetByName('Заявки');

    // защита от дублей по ключу идемпотентности
    var ids = sh.getRange('B2:B').getValues().flat();
    if (ids.indexOf(d.id) !== -1) {
      return ContentService.createTextOutput('dup');
    }

    sh.appendRow([
      new Date(d.ts), d.id, d.name, d.phone, d.email,
      d.message, d.page, d.utm, d.ip, 'Новая', '', ''
    ]);
    return ContentService.createTextOutput('ok');
  } finally {
    lock.releaseLock();
  }
}

Двадцать строк кода, ноль подписок. Единственное, за чем нужно следить: при каждом изменении скрипта Apps Script требует создать новую версию развёртывания, иначе на боевом URL продолжает крутиться старый код. Меня это подловило дважды.

Уведомление в мессенджер добавляется тем же способом, я разбирал его отдельно в статье про интеграцию формы с Telegram-ботом. Связка получается такая: в Telegram владелец видит заявку через секунду, в таблице она лежит для истории и отчётов.

Что писать в строку

Половина смысла таблицы теряется, когда в ней три колонки: имя, телефон и время. Через месяц владелец спрашивает, откуда пришли эти люди, и ответить нечем.

КолонкаЧто кладёмЗачем
Дата и времяISO-строка от сервераСчитать скорость ответа и загрузку по дням недели
IDслучайные 16 символовКлюч против дублей, ссылка на строку в логе
Имякак ввёл посетительОбращение при звонке
Телефоннормализованный к +7XXXXXXXXXXИначе поиск дублей не работает
Комментарийтекст из формыПоловина квалификации лида уже здесь
СтраницаURL, откуда отправлена формаВидно, какая услуга реально продаёт
UTMsource, medium, campaign одной строкойРазделить контекст, органику и переходы
Статусвыпадающий списокНовая, В работе, Счёт, Оплачено, Отказ
Ответственныйимя менеджераЕсли их двое
Заметкасвободный текстЧто обсудили по телефону

Про UTM отдельно. Метки надо тащить не из текущего URL, а из первого визита: посетитель приходит по рекламе, гуляет три дня, возвращается из закладок и отправляет форму. Если читать метки в момент отправки, вся реклама превратится в «прямые заходы». Метки складываются в cookie на первом визите и оттуда подставляются в скрытое поле формы. Механику я расписывал в материале про UTM-метки для контекста.

Скрытые поля формы, которые стоит завести сразу:

<input type="hidden" name="page_url" value="">
<input type="hidden" name="utm" value="">
<input type="hidden" name="company_website" tabindex="-1" autocomplete="off">

Последнее — honeypot. Ботов на формах в разы больше, чем людей, и без ловушки таблица за месяц зарастает мусором вида «Best SEO offer». Заполняется поле только скриптом, живой человек его не видит.

Персональные данные в облачной таблице

Здесь начинается часть, которую подрядчики обычно пропускают, а потом клиент получает запрос из Роскомнадзора и удивляется.

Имя, телефон, почта и комментарий — это персональные данные. Как только вы кладёте их в облачную таблицу, вы поручаете обработку оператору этого облака. Если сервера физически за границей, это трансграничная передача, и с 2023 года она требует отдельного уведомления в РКН до начала передачи. Просто галочки в согласии тут мало. Тему я разбирал подробно в статье про трансграничную передачу данных, пересказывать не буду, скажу главное на практике.

Что реально нужно сделать, если данные всё-таки едут в зарубежное облако:

  1. В согласии на обработку прямо перечислить, кому передаются данные и с какой целью.
  2. Подать уведомление об обработке ПДн, а для трансграничной передачи — отдельное уведомление о намерении.
  3. Ограничить доступ к таблице поимённо. Доступ по ссылке «всем в интернете» на таблицу с телефонами клиентов — это готовый инцидент.
  4. Настроить срок хранения. Заявки трёхлетней давности не нужны, а ответственность за их утечку никуда не делась.
  5. Не складывать в таблицу лишнее. Паспорт, дата рождения, адрес доставки в таблице с общим доступом — плохая идея в любой конфигурации.

Есть и вариант проще: держать в облаке только обезличенную часть. Дата, источник, страница, статус, сумма. Имя и телефон живут в логе на вашем сервере и в мессенджере, а в таблице лежит только ID. Отчётность по маркетингу работает полностью, а персональных данных в чужом облаке нет. Для владельца, который хочет спать спокойно и не разбираться в уведомлениях, это лучший компромисс из тех, что я пробовал.

Российские альтернативы

Когда клиент говорит «давай без иностранных сервисов», у меня есть короткий список того, что реально работает в 2026 году.

ВариантКак принимает заявкиОриентир по цене
Яндекс Документы (таблицы)вебхук через Яндекс Формы или свой скрипт-прослойкабесплатно, платный бизнес-тариф от 300 руб. за пользователя в месяц
Р7-Офис, облакоREST API документов, макросыот 400 руб. за пользователя в месяц
МойОфис Частное облакоAPI, разворачивается у себялицензия от нескольких десятков тысяч, для микробизнеса дорого
NocoDB или Baserow на своём VPSштатный REST API, вебхукиVPS от 600 руб. в месяц, софт бесплатный
Обычный CSV на своём хостингепишет тот же обработчик0 руб., открывается любым офисом

Отдельно похвалю связку NocoDB на маленьком VPS: получается таблица с человеческим интерфейсом, с фильтрами и правами доступа, при этом данные лежат на вашем сервере в вашей стране. Ставится за час, ест 512 МБ памяти. Для клиента с чувствительными данными я обычно предлагаю именно это.

Самописный CSV недооценивают зря. Файл на сервере, права 600, скачивается по кнопке из закрытого раздела. Никакой облачной обвязки, никаких уведомлений о трансграничной передаче, открывается в чём угодно. Минус один: совместная работа отсутствует как класс.

Дубли и потери

Интеграция без обработки сбоев ломается тихо. Заявки перестают доходить, а узнают об этом через неделю по звонку возмущённого клиента «я вам писал, вы не ответили».

Откуда берутся потери:

  • Apps Script упёрся в квоту (лимиты на количество вызовов и время исполнения никто не отменял).
  • Сменили развёртывание, старый URL умер, а в mail.php остался прежний.
  • Хостинг закрыл исходящие соединения после смены тарифа.
  • Кончилось место на диске, и запись в лог отвалилась вместе со всем остальным.

Откуда берутся дубли: посетитель дважды жмёт кнопку, скрипт ретраит после таймаута, а также редирект Apps Script, из-за которого запрос иногда исполняется дважды. Лечится ключом идемпотентности, который я закладывал в код выше, и проверкой по колонке B.

Дозаливка потерянного делается кроном раз в 15 минут:

#!/bin/bash
# /home/c/user/site/private/retry.sh
F=/home/c/user/site/private/leads-retry.log
[ -s "$F" ] || exit 0
mv "$F" "$F.work"
while IFS= read -r line; do
  code=$(curl -sL -o /dev/null -w '%{http_code}' -m 10 
    -H 'Content-Type: application/json' -d "$line" "$SHEET_WEBHOOK_URL")
  [ "$code" = "200" ] || echo "$line" >> "$F"
done < "$F.work"
rm -f "$F.work"

И контрольный вопрос, который решает больше, чем весь код: как вы узнаете, что заявок нет, потому что их правда нет, а не потому что сломалась интеграция. Я обычно вешаю простую проверку: если за 72 часа в таблицу не приехало ни одной строки, скрипт шлёт в Telegram напоминание проверить форму. Ложные срабатывания на низком трафике бывают, но лучше лишний раз проверить, чем месяц собирать заявки в мусорку.

Чек-лист приёмки, по которому я сдаю такую интеграцию клиенту:

  • Тестовая заявка приходит во все каналы: почта, мессенджер, таблица, лог.
  • Двойной клик по кнопке даёт одну строку, а не две.
  • Отключённый интернет на стороне таблицы не ломает форму (проверяется подменой URL вебхука на несуществующий).
  • Заявка с кириллицей, длинным комментарием и пустым email не ломает разбор.
  • Бот с honeypot отсекается и в таблицу не попадает.
  • Доступ к таблице выдан поимённо, публичная ссылка отозвана.
  • Файлы с ПДн лежат вне docroot и не отдаются по прямой ссылке.

Последний пункт проверяется одной командой: curl -I https://вашсайт.ру/private/leads.log. Ответ должен быть 403 или 404, а не 200 со списком телефонов.

Когда таблица начинает мешать

Признаки, что пора двигаться к нормальной системе:

  • Два и более менеджера спорят, кто взял заявку в работу.
  • Появились этапы сделки и нужен отчёт по конверсии между ними.
  • Звонки надо слушать, а не пересказывать в колонке «заметка».
  • Заявки идут не только с сайта, а ещё из мессенджеров, с Авито и с почты.
  • Кто-то один раз отсортировал диапазон без заголовков и перемешал данные (классика, восстанавливать приходится из истории версий).
  • Больше 60-80 заявок в месяц.

Совпало три пункта: считайте, что таблица уже стоит вам денег в виде потерянных лидов. Дальше выбор между amoCRM и Битрикс24, про их отличия и цену внедрения я писал в разборе интеграции с amoCRM и Битрикс24. Хорошая новость: накопленная таблица переезжает в CRM импортом за час, если колонки заведены так, как я описал выше. Плохая: если в ней три колонки и каша в телефонах, переезд превращается в ручную чистку на несколько дней.

Промежуточный вариант, который часто спасает: оставить таблицу как приёмник и добавить к ней канбан-доску в любом таск-трекере. Заявка падает в таблицу, менеджер руками создаёт карточку по тем, кто ответил. Черновой поток отделён от рабочих сделок, а платить за CRM пока не надо.

Что делать дальше

Если у вас на сайте сейчас работает только письмо на почту, начните с малого: добавьте в обработчик запись в лог и уведомление в мессенджер. Это полчаса работы и минус один класс проблем. Таблицу подключайте следующим шагом, когда захотите смотреть на источники.

Настройка такой связки под ключ у меня занимает 2-4 часа: обработчик, скрытые поля с UTM, приёмник в таблице, защита от дублей, крон на дозаливку и приёмка по чек-листу. Для клиентов на абонентском обслуживании это входит в часы тарифа, отдельной строкой не считается. Что входит в тарифы и сколько это стоит, смотрите в разделе с ценами.

Если заявки уже теряются и разбираться некогда, напишите через форму на главной и приложите ссылку на страницу с формой. Обычно я за вечер говорю, что именно у вас отваливается: спам-фильтр на почте, отвалившийся вебхук или форма, которая молча падает на мобильных.

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

Когда таблицы хватает вместо CRM

При потоке до нескольких десятков заявок в месяц и одном-двух менеджерах. Дальше начинается боль. CRM нужна, когда появляются этапы сделки, распределение заявок между людьми и история переписки по каждому клиенту.

Можно ли складывать заявки в зарубежное облако

Формально это трансграничная передача персональных данных со всеми требованиями. Российские аналоги закрывают ту же задачу и снимают вопрос целиком.

Что писать в строку таблицы

Дату и время, имя, контакт, текст заявки, страницу, с которой пришла, и источник перехода. Последние два поля потом отвечают на вопрос, какая реклама окупается.

Как не потерять заявки

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

Передайте техническую часть в надёжные руки.

Слежу за хостингом, бэкапами и обновлениями. Когда что-то пойдёт не так – звоните мне, не своему программисту, не в спортзал и не маме.

Короткие вопросы по теме

Все вопросы по теме "Юр. сторона и интеграции"

Заявка

Обсудим задачу

Напишите имя и телефон, выберите удобный канал. Перезвоню или напишу в течение часа в рабочее время.

Куда удобнее ответить?