Интеграция Airtable и n8n: upsert записей, формы и защита от дублей ¶
Обновлено: 2026-05-30
Импортируйте JSON в n8n, замените credentials, URL API, project/list IDs, поля и лимиты под вашу инфраструктуру.
Проблема: Airtable часто используют как быструю CRM или операционную базу, но простой create record на каждый webhook быстро создаёт дубли и ломает представления.
Решение: Надёжная интеграция Airtable и n8n должна нормализовать входные поля, собрать стабильный external_id, найти существующий record через filterByFormula и выполнить update или create только после проверки.
Проблема: почему простая интеграция создаёт дубли и ручной хаос ¶
Airtable удобен как рабочая база для маркетинга, контента, продаж и поддержки: таблицы понятны команде, views заменяют лёгкую CRM, а формы быстро собирают данные. Проблема начинается, когда данные приходят из нескольких источников: Tilda, Telegram, email, CRM, платежи и ручной импорт.
Если workflow каждый раз вызывает create record, в базе появляются одинаковые строки с разными статусами, attachment fields теряют связь с исходной заявкой, а менеджеры перестают доверять views. Поэтому задача статьи — не “подключить Airtable”, а построить upsert-слой между n8n и базой.
Архитектура workflow для n8n ¶
| Блок | Задача | Production-проверка |
|---|---|---|
| Webhook / source event | принимает форму, CRM-событие или ручной payload | source, entity_id, timestamp |
| Normalize Airtable fields | приводит телефон, email, status и tags к единому виду | нет пустого external_id |
| Find record | ищет строку через filterByFormula по external_id | поиск выполняется до create |
| Update or create record | обновляет найденный record или создаёт новый | правильный baseId, tableId и field names |
| Attachments / comments | добавляет ссылки и файлы без потери контекста | attachment URLs доступны Airtable |
| Respond / alert | возвращает результат и пишет ошибку в alert | без секретов и лишних PII |
Для production лучше иметь отдельное поле External ID с уникальным бизнес-ключом. Формула поиска должна использовать это поле, а не title или имя клиента.
Контракт входных данных ¶
{
"source": "landing_form",
"entity_id": "lead-2026-10492",
"name": "Алексей",
"phone": "+7 (916) 111-22-33",
"email": "alexey@example.ru",
"status": "new",
"tags": [
"n8n",
"crm",
"priority"
],
"budget": 150000,
"source_url": "https://example.ru/request/10492",
"attachments": [
{
"url": "https://example.ru/files/brief.pdf",
"filename": "brief.pdf"
}
]
}Минимальный контракт: source, entity_id и хотя бы одно поле для человека — name, email или phone. В Airtable нельзя полагаться на название записи как на ключ дедупликации.
Code Node: нормализация, mapping и guard-условия ¶
const src = $json.body ?? $json;
const source = String(src.source ?? 'external').trim().toLowerCase();
const entityId = String(src.entity_id ?? src.id ?? '').trim();
if (!entityId) throw new Error('No entity_id for Airtable upsert');
const rawPhone = String(src.phone ?? '').trim();
let digits = rawPhone.replace(/\D/g, '');
if (digits.length === 11 && digits.startsWith('8')) digits = `7${digits.slice(1)}`;
if (digits.length === 10) digits = `7${digits}`;
const email = String(src.email ?? '').trim().toLowerCase();
const tags = Array.isArray(src.tags) ? src.tags.map(String).slice(0, 10) : [];
const externalId = `${source}:${entityId}`;
const filterByFormula = `{External ID} = '${externalId.replace(/'/g, "\'")}'`;
return [{ json: {
external_id: externalId,
filterByFormula,
fields: {
'External ID': externalId,
'Name': String(src.name ?? 'Новая запись').trim(),
'Phone': digits ? `+${digits}` : '',
'Email': email,
'Status': String(src.status ?? 'new').trim(),
'Tags': tags,
'Budget': Number(src.budget ?? 0),
'Source URL': String(src.source_url ?? '').trim(),
'Last synced at': new Date().toISOString(),
'Attachments': Array.isArray(src.attachments) ? src.attachments.map(a => ({ url: a.url, filename: a.filename })) : []
}
}}];
Почему не стоит искать запись по имени
Имя, название компании и тема заявки меняются. External ID связывает запись с исходной системой и позволяет безопасно обновлять Airtable даже после ручных правок в view.
Готовый workflow JSON: скачать и импортировать ¶
Скачать готовый workflow JSON Скачать тестовый payload
{
"name": "Nodbot - Airtable upsert records with external id",
"nodes": [
{
"name": "Airtable Webhook",
"type": "n8n-nodes-base.webhook",
"purpose": "Принять событие"
},
{
"name": "Normalize Airtable fields",
"type": "n8n-nodes-base.code",
"purpose": "Собрать fields и filterByFormula"
},
{
"name": "Find Airtable record",
"type": "n8n-nodes-base.httpRequest",
"purpose": "Найти record по External ID"
},
{
"name": "Create or update record",
"type": "n8n-nodes-base.httpRequest",
"purpose": "Сделать create/update"
},
{
"name": "Respond",
"type": "n8n-nodes-base.respondToWebhook",
"purpose": "Вернуть статус"
}
],
"connections": "Airtable Webhook → Normalize Airtable fields → Find Airtable record → Create or update record → Respond"
}
Пошаговая настройка связки ¶
- Создайте в Airtable поле External ID и не переименовывайте его без миграции workflow.
- Импортируйте workflow JSON и замените baseId, tableId, credential и field names.
- Проверьте filterByFormula на тестовой записи до production.
- Настройте handling для attachments: Airtable должен иметь доступ к URL файла.
- Отправьте одинаковый payload дважды и убедитесь, что обновляется одна запись.
Тесты перед production ¶
curl -X POST "https://YOUR-N8N-DOMAIN/webhook/integration-airtable-n8n-upsert-records" \
-H "Content-Type: application/json" \
--data @integration-airtable-n8n-upsert-records-payload.json- Повторный payload не создаёт дубль и возвращает тот же output key.
- Некорректный mapping останавливается до запроса к внешнему API.
- Пустые необязательные поля не ломают workflow.
- Ошибка API уходит в alert или DLQ с безопасным payload.
- Execution data не содержит секретов, токенов и лишних персональных данных.
Production-риски ¶
- Поиск по title. Ручная правка названия создаст дубль при следующей синхронизации.
- Переименованы поля. Airtable API начнёт возвращать ошибку или писать не туда.
- Attachment URL недоступен. Файл не прикрепится, хотя record будет создан.
- Нет лимитов и retry. Массовый импорт может упереться в API rate limits.
- Секреты в execution data. Не логируйте personal access token и полный payload без маскирования.
Полезные ссылки и смежные материалы ¶
- Airtable Web API documentation
- Airtable List records
- Airtable Create records
- Webhook idempotency to Postgres
- Google Sheets и n8n
Критерии готовности ¶
- External ID есть в таблице и используется для поиска.
- Повторный payload обновляет один record, а не создаёт второй.
- Attachments проходят тест с реальным URL.
- Ошибки Airtable API уходят в alert или DLQ.
- У workflow есть владелец, версия и тестовый payload.
Nodbot настроит Airtable + n8n: upsert, mapping полей, attachment handling, retry, alert и тестовые payload без дублей в базе.
Обсудить Airtable-интеграцию