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

Надежное исправление строится вокруг идентификатора заявки и идемпотентной записи: один и тот же запрос можно получить несколько раз, но в Google Sheets или Excel он должен создать только одну строку.

Сначала остановите потерю контроля

  • Не удаляйте массово строки без резервной копии таблицы.
  • Временно отключите лишние триггеры и повторяющиеся сценарии, если источник уже понятен.
  • Сохраните время появления дублей и несколько пар одинаковых строк.
  • Не публикуйте в диагностических логах телефоны, email, токены и полный текст обращения.
  • Зафиксируйте текущие версии Apps Script, webhook и настроек формы.

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

Определите, что считать одной заявкой

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

  • lead_id — уникальный идентификатор заявки в основной системе;
  • event_id — идентификатор события webhook;
  • source — сайт, форма, рекламная площадка или CRM;
  • created_at — время создания в источнике, а не время записи в таблицу;
  • processed_at — время успешной обработки интеграцией;
  • payload_hash — дополнительный контроль содержимого, но не единственный ключ.

Телефон, email и точное время не подходят как единственный ключ. Контакт может отправить несколько разных заявок, формат телефона меняется, а часы разных систем могут расходиться.

Найдите источник повторной записи

Сравните пары дублей по времени, идентификаторам и техническим полям. Характер интервала часто подсказывает причину.

  • Строки появляются почти одновременно — двойной submit, два обработчика или параллельные trigger.
  • Повтор возникает через несколько секунд — webhook не получил быстрый успешный ответ и повторил доставку.
  • Дубли приходят по расписанию — poller каждый раз забирает старый диапазон без checkpoint.
  • Повтор появляется после ошибки — первая запись прошла, но клиент получил timeout и отправил retry.
  • Одинаковые заявки приходят из разных колонок — одновременно работают прямая интеграция и CRM-сценарий.
  • Дубли возникают только после редактирования листа — onEdit или onChange запускает обработку повторно.

Проверьте форму на двойную отправку

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

  • На submit установлен один обработчик, а не несколько после повторной инициализации компонента.
  • Кнопка не находится одновременно внутри form и в отдельном click-сценарии отправки.
  • После успешного ответа показывается результат, а повторное нажатие не запускает тот же запрос.
  • Сервер создает lead_id один раз и возвращает его клиенту.
  • Повтор с тем же idempotency key возвращает прежний результат без новой записи.

Учитывайте повторную доставку webhook

Большинство webhook работают по модели at least once: поставщик имеет право прислать одно событие повторно, если не получил подтверждение или сомневается в результате. Это штатное поведение, а не ошибка сервиса.

  • Проверяйте подпись и источник webhook до обработки.
  • Сохраняйте уникальный event_id в журнале полученных событий.
  • Повторный event_id подтверждайте без повторного append строки.
  • Тяжелую обработку передавайте в очередь, а webhook отвечайте в допустимый срок.
  • Не отмечайте событие выполненным до успешной фиксации результата.
  • Разделяйте статусы received, processing, processed и failed.

Сделайте обработку идемпотентной

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

  1. Получить и проверить стабильный lead_id или event_id.
  2. Атомарно зарегистрировать ключ в хранилище обработки.
  3. Если ключ уже существует со статусом processed, вернуть прежний успешный результат.
  4. Если обработка не завершена, не запускать второй конкурентный append.
  5. Записать строку в таблицу.
  6. Сохранить номер строки или внешний идентификатор результата.
  7. Только затем отметить событие как processed.

Не используйте поиск по всей таблице как единственную блокировку

Сценарий «найти lead_id в листе и, если его нет, добавить строку» уязвим к гонке. Два запуска могут одновременно не найти запись и оба выполнить append. Кроме того, поиск становится медленным по мере роста листа.

Для надежной регистрации ключей лучше использовать базу с уникальным индексом, PropertiesService для небольшого сценария с ограничениями, Cache только как ускоритель либо отдельный журнал событий. Выбор зависит от нагрузки и критичности заявок.

Если используется Google Apps Script

  • Проверьте список установленных trigger: одинаковая функция может быть добавлена несколько раз.
  • Не смешивайте простой onEdit и установленный trigger для одного действия без явной причины.
  • Используйте LockService вокруг критической секции проверки и append.
  • Ограничивайте время блокировки и корректно освобождайте lock.
  • Не запускайте обработку всего листа при каждом изменении одной ячейки.
  • Храните checkpoint для плановой выгрузки и обновляйте его только после успеха.
  • Записывайте execution id и lead_id в технический журнал.

LockService уменьшает риск параллельного append, но не заменяет стабильный идентификатор и журнал обработки. После timeout выполнение могло завершиться, а вызывающая система все равно отправит событие повторно.

Проверьте триггеры и сценарии автоматизации

В Make, n8n, Zapier и похожих системах один источник может быть подключен к двум активным сценариям. Иногда старый сценарий остается включенным после переноса, а новая версия делает ту же запись параллельно.

  • Составьте список всех маршрутов от формы до таблицы.
  • Проверьте production, test и старые копии сценариев.
  • Сравните webhook URL в форме, CRM и интеграции.
  • Найдите retry-ветки, которые повторяют весь сценарий после частичной ошибки.
  • Проверьте расписания на пересечение и одновременный запуск.
  • Оставьте один ответственный маршрут записи в конкретный лист.

Разделите прием заявки и экспорт в таблицу

Google Sheets удобен для просмотра и совместной работы, но не должен быть единственным местом приема критичной заявки. Надежнее сначала сохранить обращение в базе или CRM с уникальным ID, а затем экспортировать его в таблицу.

  • Сайт подтверждает заявку после записи в основное хранилище.
  • Отдельный worker переносит данные в таблицу.
  • Ошибка Google API не приводит к потере обращения.
  • Повторный экспорт использует тот же lead_id.
  • Состояние синхронизации можно проверить и повторить вручную.

Обрабатывайте timeout правильно

Timeout не означает, что операция не выполнена. Google API мог добавить строку, но ответ не успел вернуться. Слепой повтор append создаст дубль. После неопределенного результата проверьте состояние по lead_id или используйте заранее подготовленную идемпотентную операцию.

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

Как безопасно очистить уже созданные дубли

  1. Сделайте копию листа и экспорт исходных данных.
  2. Определите ключ дедупликации и период, где он надежен.
  3. Сгруппируйте строки по lead_id или event_id.
  4. Сравните содержимое повторов и связанные статусы.
  5. Выберите каноническую строку по бизнес-правилу.
  6. Перенесите уникальные комментарии и результаты обработки.
  7. Сначала пометьте повторы, затем удаляйте после проверки.
  8. Исправьте источник дублей до следующего запуска интеграции.

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

Добавьте технические колонки

  • lead_id или event_id;
  • source и form_id;
  • source_created_at;
  • synced_at;
  • integration_version;
  • processing_status;
  • last_error_code без секретов и персональных данных.

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

Как проверить исправление

  1. Отправьте одну тестовую заявку и проверьте единственную строку.
  2. Повторите тот же запрос с тем же idempotency key.
  3. Запустите две одинаковые обработки параллельно.
  4. Смоделируйте timeout после фактической записи строки.
  5. Повторно доставьте webhook с тем же event_id.
  6. Проверьте временную ошибку Google API и ограниченный retry.
  7. Убедитесь, что новая отдельная заявка того же клиента не блокируется.
  8. Сверьте журнал событий, основную базу и итоговую таблицу.

Типичные неправильные решения

  • Удалять одинаковые строки раз в сутки, не исправляя источник.
  • Считать телефон уникальным идентификатором заявки.
  • Отключить кнопку после клика и не защищать сервер.
  • Проверять наличие ID и делать append без атомарной блокировки.
  • Повторять весь сценарий после любой ошибки.
  • Считать timeout доказательством неуспешной записи.
  • Хранить единственную копию заявки только в таблице.
  • Выводить токены и полный payload в общедоступный журнал.

Профилактика

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

  • Мониторьте долю повторных event_id и ошибки записи.
  • Проверяйте список trigger после обновления Apps Script.
  • Не запускайте старую и новую версии интеграции одновременно.
  • Храните checkpoint и журнал синхронизации.
  • Периодически сверяйте число уникальных заявок в источнике и таблице.

Итог

Когда таблица дублирует строки заявок, причина обычно в повторной доставке, параллельной обработке или отсутствии устойчивого ID. Идемпотентный серверный прием, атомарная регистрация события, контроль trigger и безопасный retry устраняют дубли без блокировки реальных повторных обращений.

Если нужно исправить такую автоматизацию, я могу проследить маршрут заявки от формы до Google Sheets или Excel, найти повторные trigger и retry, внедрить lead_id и идемпотентность, очистить существующие дубли по проверенному правилу и настроить мониторинг синхронизации.