---
title: "n8n + Google Sheets: upsert строки по телефону | Nodbot"
source_url: "https://nodbot.ru/workflows/google-sheets-upsert-by-phone/"
canonical_url: "https://nodbot.ru/workflows/google-sheets-upsert-by-phone/"
language: "ru"
content_type: "WorkflowTemplate"
section: "workflows"
generated_at: "2026-05-30"
word_count_source: 998
---

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

## AI summary

Практический шаблон n8n для upsert в Google Sheets по телефону: нормализация номера, встроенный Append or Update Row, защита от дублей, тестовый payload и production-чеклист.

## Best used for

Материал помогает внедрить страницу как production-runbook: понять интент, проверить риски, сохранить owner и не смешивать тему с соседними сценариями.

## Key topics

- Когда нужен upsert, а не append
- Архитектура workflow
- Контракт входных данных
- Нормализация телефона в Code node
- Импортируемый workflow JSON
- Настройка Google Sheets
- Тесты на дубли
- Production-риски
- Критерий готовности

## Source outline

# Google Sheets + n8n: upsert строки по телефону без дублей [¶](#google-sheets-n8n-upsert-stroki-po-telefonu-bez-dubley "Permanent link")

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

Сохранить в мой план [Открыть мой план](/my-plan/)

**Шаблон для внедрения**

[Скачать workflow JSON](/assets/workflows/google-sheets-upsert-by-phone.json)[Скачать test payload](/assets/workflows/google-sheets-upsert-by-phone-payload.json)Скопировать curl

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

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

![Схема workflow n8n для Google Sheets upsert по телефону](/assets/images/workflows/google-sheets-upsert-by-phone-graph.svg)

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

## Когда нужен upsert, а не append [¶](#kogda-nuzhen-upsert-a-ne-append "Permanent link")

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

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

## Архитектура workflow [¶](#arhitektura-workflow "Permanent link")

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

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

## Контракт входных данных [¶](#kontrakt-vhodnyh-dannyh "Permanent link")

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 [¶](#normalizatsiya-telefona-v-code-node "Permanent link")

Ключевой шаг — получить одинаковое значение из форматов `+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 [¶](#importiruemyy-workflow-json "Permanent link")

Полный 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 [¶](#nastroyka-google-sheets "Permanent link")

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: лучше сначала изменить ноду, затем таблицу.

## Тесты на дубли [¶](#testy-na-dubli "Permanent link")

Один успешный запуск не доказывает готовность. Отправьте 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-риски [¶](#production-riski "Permanent link")

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

## Критерий готовности [¶](#kriteriy-gotovnosti-google-sheets-upsert "Permanent link")

Workflow можно включать в production, когда повторный payload обновляет строку, номер нормализуется одинаково, ошибки Google Sheets попадают в alert, таблица имеет владельца, а команда знает, где менять mapping. Для более строгой защиты от повторов используйте [Postgres-идемпотентность](/workflows/webhook-idempotency-to-postgres/), а для обработки временных ошибок — [retry и dead-letter queue](/workflows/retry-dlq-http-request/).
