Перейти к содержанию

Google Sheets + n8n: upsert строки по телефону без дублей

Обновлено: 2026-05-30

Открыть мой план
Шаблон для внедрения

Используйте готовый JSON workflow как основу: после импорта замените Google credential, spreadsheetId, название листа и проверьте правило нормализации телефона на своих номерах.

Задача workflow — не просто добавить лид в таблицу, а сохранить одну актуальную строку на один телефон. Повторная заявка из формы, Telegram, VK, CRM или лендинга должна обновить существующую запись, а не создать дубль. Так менеджер видит чистую таблицу, а последующие автоматизации не отправляют повторные сообщения одному и тому же человеку.

Схема workflow n8n для Google Sheets upsert по телефону
Схема потока: Webhook принимает заявку, Code нормализует телефон, Google Sheets делает append или update по колонке phone_normalized, Webhook возвращает безопасный ответ.

Когда нужен upsert, а не append

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

Upsert работает иначе: workflow использует стабильный ключ, ищет существующую запись и либо обновляет её, либо добавляет новую строку. В российских продажах таким ключом часто удобнее делать телефон: email может быть разным, а номер нужен для звонка, WhatsApp, Telegram и CRM.

Архитектура workflow

НодаРольЧто проверить
Webhook inputпринимает lead payloadметод POST, production URL, JSON body
Normalize phoneсоздаёт единый ключ+7, 8, пробелы, скобки, пустые значения
Append or update lead rowпишет в Google Sheetsmatching column: phone_normalized
Respond to Webhookвозвращает технический ответбез токенов, лишних персональных данных и сырого payload

Базовый вариант использует встроенную операцию Google Sheets Append or Update Row. Ручная схема Find row → IF → Update/Append нужна, когда у вас есть сложные ветки: например, не обновлять источник первого лида, писать audit trail, отправлять alert при конфликте или проверять несколько ключей.

Контракт входных данных

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

{
  "phone": "+7 (999) 111-22-33",
  "name": "Сергей",
  "email": "sergey@example.ru",
  "source": "vk_lead_form",
  "external_id": "lead-58391",
  "received_at": "2026-05-30T10:00:00Z"
}

Если телефон пустой или не приводится к нормальному виду, workflow должен остановиться до записи. Не подставляйте фиктивные значения вроде unknown: такие строки потом невозможно дедуплицировать и опасно отправлять в CRM.

Нормализация телефона в Code node

Ключевой шаг — получить одинаковое значение из форматов +7 (999) 111-22-33, 8 999 111 22 33 и 9991112233. Для российской базы лидов удобно хранить ключ в формате +79991112233.

const input = $json.body ?? $json;
const raw = String(input.phone ?? '').trim();
let digits = raw.replace(/\D/g, '');

if (digits.length === 11 && digits.startsWith('8')) {
  digits = `7${digits.slice(1)}`;
}
if (digits.length === 10) {
  digits = `7${digits}`;
}
if (!/^7\d{10}$/.test(digits)) {
  throw new Error(`Invalid phone: ${raw}`);
}

return [{
  json: {
    phone_raw: raw,
    phone_normalized: `+${digits}`,
    name: input.name ?? '',
    email: input.email ?? '',
    source: input.source ?? 'unknown',
    external_id: input.external_id ?? input.lead_id ?? '',
    first_seen_at: input.received_at ?? new Date().toISOString(),
    updated_at: new Date().toISOString(),
    update_count: Number(input.update_count ?? 0) + 1
  }
}];

Эта нода возвращает phone_raw для аудита и phone_normalized для поиска. В Google Sheets именно phone_normalized должен быть выбран как matching column. Если вы работаете с международными номерами, замените правило на библиотеку или отдельный справочник стран, а не расширяйте регулярку хаотично.

Импортируемый workflow JSON

Полный JSON лежит в архиве и доступен по кнопке «Скачать workflow JSON». Ниже — сокращённая структура, чтобы сразу было видно, что это именно workflow n8n, а не только пример входного payload.

{
  "name": "Nodbot - n8n Google Sheets upsert by phone",
  "nodes": [
    {
      "name": "Webhook input",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "httpMethod": "POST",
        "path": "google-sheets-upsert-by-phone",
        "responseMode": "responseNode"
      }
    },
    {
      "name": "Normalize phone",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "// full code is shown above in the article"
      }
    },
    {
      "name": "Append or update lead row",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": "YOUR_SPREADSHEET_ID",
        "sheetName": "Leads",
        "matchingColumns": [
          "phone_normalized"
        ]
      }
    },
    {
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook"
    }
  ],
  "connections": {
    "Webhook input": "Normalize phone → Append or update lead row → Respond to Webhook"
  }
}

После импорта откройте ноду Google Sheets, выберите credential, укажите spreadsheetId и лист Leads, затем обновите схему колонок. Если n8n не подтянул новые заголовки после изменения таблицы, заново выберите mapping mode и пересохраните ноду.

Настройка Google Sheets

  1. Создайте отдельный production-лист, а не используйте тестовую таблицу с демо-данными.
  2. Добавьте колонки phone_raw, phone_normalized, name, email, source, external_id, first_seen_at, updated_at и update_count.
  3. Выдайте Google credential доступ только к нужной таблице, а не ко всему Drive аккаунта.
  4. Не переименовывайте лист вручную после публикации workflow: лучше сначала изменить ноду, затем таблицу.

Тесты на дубли

Один успешный запуск не доказывает готовность. Отправьте payload дважды: первый запуск должен создать строку, второй — обновить её по phone_normalized. Затем измените имя при том же телефоне и убедитесь, что новая строка не появилась.

curl -X POST "https://YOUR-N8N-DOMAIN/webhook/google-sheets-upsert-by-phone" \
  -H "Content-Type: application/json" \
  --data @google-sheets-upsert-by-phone-payload.json

Дополнительно проверьте номер в формате 8 999 111 22 33, пустой телефон, лишние пробелы и повторный external_id. Если тесты создают дубли, сначала смотрите matching column, потом нормализацию, затем права Google credential.

Production-риски

  • Гонки при одновременных заявках. Если два события с одним телефоном пришли почти одновременно, оба могут успеть создать строку. Для критичных сценариев используйте Webhook + Postgres idempotency.
  • Лимиты Google API. Не запускайте массовый импорт через тот же workflow без batch-логики, retry и backoff.
  • Ручные правки таблицы. Если менеджер удалил колонку или переименовал лист, нода начнёт падать или писать не туда.
  • Персональные данные в executions. Ограничьте сохранение execution data и не логируйте полный payload без необходимости.
  • Смешение журнала и карточки лида. Для истории событий заведите отдельный append-only лист, а этот workflow оставьте для актуального состояния клиента.

Критерий готовности

Workflow можно включать в production, когда повторный payload обновляет строку, номер нормализуется одинаково, ошибки Google Sheets попадают в alert, таблица имеет владельца, а команда знает, где менять mapping. Для более строгой защиты от повторов используйте Postgres-идемпотентность, а для обработки временных ошибок — retry и dead-letter queue.