Пошаговое руководство по интеграции amoCRM с сайтом: REST API, вебхуки, передача UTM-меток, склеивание дублей и распределение сделок. Реальные технические решения для разработчиков и владельцев бизнеса.
Интеграция amoCRM с сайтом через REST API и вебхуки — это единственный способ получить технически корректную автопередачу заявок без потери UTM-меток и дублей лидов. В статье — архитектура склеивания в amoCRM, протоколы OAuth 2.0 и JWT, пошаговый чек-лист настройки и тонкости работы с PHP cURL.
Архитектура автосинхронизации: REST API, вебхуки и брокеры очередей
Прямая передача заявки из HTML-формы в amoCRM без буферизации — риск потерять данные при пиковых нагрузках. В промышленной интеграции мы используем цепочку: Форма → Веб-сервер (Nginx) → Очередь (Redis/Beanstalkd) → REST API amoCRM. Это гарантирует идемпотентность: если amoCRM временно недоступна (код 503 или 429), запрос не теряется, а попадает в retry-цикл.
Ваш PHP-скрипт (или Node.js/Go) должен отправлять POST-запрос на https://{subdomain}.amocrm.ru/api/v4/leads с HTTP-заголовками:
Authorization: Bearer {access_token}
Content-Type: application/json
X-Request-Id: {uuid}
Последний заголовок критичен: при повторном запросе (из-за тайм-аута) amoCRM не создаст дубликат сделки, если X-Request-Id совпадает с предыдущим. Это стандарт RFC 7231 — идемпотентность.
OAuth 2.0: получаем и обновляем Access Token
amoCRM использует протокол OAuth 2.0 с authorization code flow. Важное правило: access token живёт 3 месяца, refresh token — 1 год. Храните их в зашифрованной БД, а не в файлах конфига. Пример получения токена на PHP (с использованием cURL):
function refreshAmoToken($clientId, $clientSecret, $refreshToken) { $ch = curl_init('https://{subdomain}.amocrm.ru/oauth2/access_token'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ 'client_id' => $clientId, 'client_secret' => $clientSecret, 'grant_type' => 'refresh_token', 'refresh_token' => $refreshToken, 'redirect_uri' => 'https://вашсайт.ру/amo/callback' ]), CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_RETURNTRANSFER => true ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); return $response;
}
В продуктовом решении мы в AVOdigital реализуем автообновление токена через cron-демон, который в согласованные сроки до истечения шлёт уведомление в Telegram-бота администратора. Никаких ручных операций.
Передача UTM-меток и склеивание лидов: протокол и поля
UTM-метки передаются в кастомные поля сделки или контакта. Рекомендуемая схема полей (создаются в amoCRM через интерфейс или API):
| Имя поля в amoCRM | Код (ID) поля | Тип данных | Пример значения | Конвертация из URL |
|---|---|---|---|---|
| UTM Source | CF_UTM_SOURCE | text | yandex_cpc | utm_source из query string |
| UTM Medium | CF_UTM_MEDIUM | text | cpc | utm_medium |
| UTM Campaign | CF_UTM_CAMPAIGN | text | brand_campaign_2025 | utm_campaign |
| UTM Content | CF_UTM_CONTENT | text | banner_main | utm_content |
| UTM Term | CF_UTM_TERM | text | ключевое слово | utm_term (URL-encode!) |
| Referrer | CF_REFERRER | text | google.com | document.referrer |
| Client ID (Яндекс) | CF_CLIENT_ID | text | 12345.67890 | _ym_uid cookie |
Склеивание лидов происходит автоматически, если вы передаёте custom_fields_values контакта с телефоном или email. amoCRM v4 включает built-in deduplication по телефону (поле custom_field_code: PHONE_WORK). Если лид пришёл с тем же номером, система добавит сделку к существующему контакту, а не создаст новый. Проверьте в настройках: Автоматическое объединение контактов должно быть включено.
На практике мы в AVOdigital столкнулись с тем, что стандартный dedup даёт коллизии при пустых номерах. Решение: на стороне сайта перед отправкой проверяем preg_match('/^\+?[1-9][0-9]{10,14}$/', $phone) и, если номер невалидный, генерируем UUID привязки к сессии.
Распределение сделок: воронка и ответственный
После создания лида его нужно направить в нужную воронку и назначить ответственного. В POST-запросе на /api/v4/leads указываем:
{ "name": "Заявка с сайта: {имя_клиента}", "pipeline_id": 123456, "status_id": 12345678, "responsible_user_id": 987654, "custom_fields_values": [ { "field_id": CF_UTM_SOURCE, "values": [{ "value": "{$utmSource}" }] }, { "field_id": CF_UTM_MEDIUM, "values": [{ "value": "{$utmMedium}" }] } ], "_embedded": { "contacts": [ { "name": "{$name}", "custom_fields_values": [ { "field_code": "PHONE_WORK", "values": [{ "value": "{$phone}" }] }, { "field_code": "EMAIL_WORK", "values": [{ "value": "{$email}" }] } ]} ] }
}
responsible_user_id можно получать динамически: запросить /api/v4/users с фильтром по группе и выбрать того, у кого минимальная загрузка (микросервис балансировки на базе очередей RabbitMQ).
Чек-лист настройки интеграции (пошаговая инструкция)
- Регистрация интеграции — в amoCRM: Админ → Интеграции → Создать внешнюю интеграцию. Записать client_id и client_secret. Redirect URI — ваш callback-скрипт.
- Установка серверной очереди — Redis или Beanstalkd. Настройка воркера, который будет потреблять сообщения из очереди и отправлять их в amoCRM.
- Получение Access Token по OAuth — выполнить authorization code flow: открыть URL вида
https://{subdomain}.amocrm.ru/oauth?client_id={id}&redirect_uri={uri}для первого раза. Получить код, обменять на токен. - Создание кастомных полей — через
POST /api/v4/leads/custom_fields(или вручную). Создать поля для UTM Source, Medium, Campaign, Content, Term. Записать field_id. - Настройка формы на сайте — AJAX-запрос к серверному скрипту. В скрипте: валидация данных, запись в БД (логирование), отправка в очередь.
- Тестирование вебхука — отправить тестовый POST-запрос через Postman или cURL. Проверить создание сделки в amoCRM.
- Настройка мониторинга — логирование ошибок в Sentry, алерт при отказе очереди или превышении лимита API (amoCRM: 10 запросов/сек, 1000/мин).
Особенности работы с Nginx и .htaccess
Если вы используете Nginx как reverse-proxy перед PHP-FPM, убедитесь, что не сбрасываются заголовки авторизации. Настройте в /etc/nginx/sites-available/default:
location /amo-proxy/ { proxy_pass http://backend:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass_header Authorization;
}
Для Apache: RewriteRule ^amo-proxy/(.*)$ /amo-proxy/handler.php [QSA,L].
Таблица: сравнение подходов к передаче данных
| Подход | Протокол | Идемпотентность | Защита от дублей | Задержка | Устойчивость |
|---|---|---|---|---|---|
| Прямой HTTP-запрос из формы | HTTP/1.1 | Нет (нет X-Request-Id) | Низкая (ручная проверка) | 100-200 мс | Низкая (блокирует пользователя) |
| Вебхук + очередь (Redis) | HTTP/1.1 + Redis LPUSH | Да (UUID + старший бит) | Высокая (проверка хэша в БД) | 1-5 сек | Высокая (очередь ретраится) |
| Вебхук + RabbitMQ | AMQP 0-9-1 | Да (delivery_tag + requeue) | Максимальная (персистентная очередь) | 5-10 сек | Очень высокая (кластер) |
Опыт AVOdigital: кейс 127 сделок в минуту
В проекте интернет-магазина с пиком трафика мы настроили интеграцию amoCRM с сайтом через RabbitMQ. Вебхук принимал заявки с трёх десятков форм. Потребовалась простая балансировка: три PHP-воркера читали очередь, и каждый отправлял данные в amoCRM с задержкой 0.5 сек (лимит API 10 запросов/сек). Добавили генерацию X-Request-Id через uniqid() + клиентский IP. Итог: 0 дублей за 3 месяца, потерянных заявок — 0.
Частые вопросы
1. Можно ли передать UTM-метки без использования JavaScript?
Да, через серверный скрипт: записывайте все UTM-параметры в сессию пользователя (PHP session или Redis) при первом заходе. При отправке формы — заберите их из сессии. JavaScript-куки здесь — только для клиентской аналитики.
2. Как избежать дублей при повторной отправке формы пользователем?
На клиенте — блокировка кнопки после первого нажатия (JS). На сервере — проверка по хэшу: md5($phone . $email . $productId . date('Y-m-d H')). Если такой хэш уже есть в БД за последний час — не создаём сделку, а возвращаем ID существующей.
3. Нужна ли подписка «Продажи» для работы с API?
Да, базовый REST API доступен только на тарифах «Продажи» и выше. Бесплатный тариф не имеет доступа к OAuth. Проверьте в настройках amoCRM: «Интеграции» → «API».
4. Что делать, если токен истёк, а заявка уже в очереди?
В очереди хранится не токен, а данные заявки. Воркер перед отправкой проверяет валидность токена (запрос GET /api/v4/account). Если ответ 401 — выполняет refresh. Если refresh токен тоже протух — отправляет уведомление администратору, и заявка остаётся в очереди до ручного вмешательства.