Многоконтрактный приём лидов с сайта
Этот документ описывает единый API-контракт, через который бэкенд сайта отправляет данные лида, контактные данные посетителя и маркетинговые UTM-метрики в ReactiveCRM одним запросом.
1. Назначение
Интегратору сайта не нужно отдельно создавать контакт, телефон, email, лид и маркетинговое событие. Один запрос должен атомарно создать:
- контакт;
- основной телефон контакта;
- основной email контакта, если указан;
- лид;
- связь между контактом и лидом;
- маркетинговое событие и UTM-атрибуцию.
Если какой-либо шаг не выполнится, весь запрос откатывается, и частично созданные данные не сохраняются.
2. Эндпоинт
POST /api/site/leads
Authorization: Bearer <CRM_ACCESS_TOKEN>
Content-Type: application/json
Токен должен передаваться только с бэкенда сайта или из serverless-функции. Никогда не помещайте CRM-токен в JavaScript в браузере.
Тенант определяется из токена авторизации. Все переданные UUID проверяются в рамках этого тенанта.
3. Полный JSON-контракт
{
"externalId": "site-form-01J7K9A2M4Y5T6",
"lead": {
"leadSourceId": "7b33b18f-55bd-48a5-a8f0-e450a56dde47",
"quality": "WARM",
"message": "Хочу проконсультироваться по внедрению CRM"
},
"contact": {
"firstName": "Ivan",
"lastName": "Ivanov",
"phone": "+79991234567",
"email": "ivan@example.com"
},
"marketing": {
"occurredAt": "2026-09-06T18:00:00Z",
"visitorId": "visitor-8b7f",
"sessionId": "session-31ac",
"source": "website",
"channel": "paid",
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "summer-consulting",
"utmContent": "banner-a",
"utmTerm": "crm consultation",
"gclid": "EAIaIQobChMI-example",
"fbclid": null,
"landingUrl": "https://example.com/consultation",
"referrer": "https://www.google.com/"
}
}
4. Поля запроса
4.1. Корневые поля
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
externalId | string | рекомендуется | Стабильный уникальный ID отправки формы для идемпотентности |
lead | object | да | Данные создаваемого лида |
contact | object | да | Данные контактного лица |
marketing | object | нет | UTM-метрики и технические данные маркетингового визита |
externalId генерируется до первой попытки отправки. При повторе запроса после тайм-аута или сетевой ошибки используйте то же значение.
4.2. Объект lead
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
leadSourceId | uuid | да | ID источника лидов в текущем тенанте |
quality | string | нет | Начальная оценка: HOT, WARM, COOL или COLD |
message | string | нет | Сообщение посетителя или комментарий к заявке |
Если сайт не выполняет предварительную квалификацию, поле quality лучше не отправлять.
4.3. Объект contact
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
firstName | string | да | Имя контактного лица |
lastName | string | нет | Фамилия контактного лица |
phone | string | да | Основной телефон; рекомендуется формат E.164 |
email | string(email) | нет | Основной email контактного лица |
Минимальный набор данных для обработки заявки — firstName и phone.
4.4. Объект marketing
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
occurredAt | date-time | нет | Время отправки формы; при отсутствии используется время CRM |
visitorId | string | нет | Анонимный ID посетителя |
sessionId | string | нет | ID сессии сайта |
source | string | нет | Источник события, по умолчанию website |
channel | string | нет | Канал: например, paid, organic, social, email, direct |
utmSource | string | нет | значение utm_source |
utmMedium | string | нет | значение utm_medium |
utmCampaign | string | нет | значение utm_campaign |
utmContent | string | нет | значение utm_content |
utmTerm | string | нет | значение utm_term |
gclid | string | нет | Идентификатор клика Google |
fbclid | string | нет | Идентификатор клика Meta/Facebook |
landingUrl | string | нет | URL посадочной страницы |
referrer | string | нет | URL предыдущей страницы |
Персональные данные не должны дублироваться в объекте marketing. Имя, телефон, email и текст сообщения передаются только в contact и lead.
5. Минимальный запрос
{
"externalId": "site-form-550e8400-e29b-41d4-a716-446655440000",
"lead": {
"leadSourceId": "7b33b18f-55bd-48a5-a8f0-e450a56dde47"
},
"contact": {
"firstName": "Ivan",
"phone": "+79991234567"
}
}
6. Успешный ответ
Первый запрос — 201 Created
{
"leadId": "22222222-2222-2222-2222-222222222222",
"contactId": "33333333-3333-3333-3333-333333333333",
"phoneId": "44444444-4444-4444-4444-444444444444",
"emailId": "55555555-5555-5555-5555-555555555555",
"marketingEventId": "66666666-6666-6666-6666-666666666666",
"duplicate": false,
"createdAt": "2026-09-06T18:00:01Z"
}
Если email или маркетинговые данные не переданы, соответствующие ID возвращаются как null.
Повтор с тем же externalId — 200 OK
{
"leadId": "22222222-2222-2222-2222-222222222222",
"contactId": "33333333-3333-3333-3333-333333333333",
"phoneId": "44444444-4444-4444-4444-444444444444",
"emailId": "55555555-5555-5555-5555-555555555555",
"marketingEventId": "66666666-6666-6666-6666-666666666666",
"duplicate": true,
"createdAt": "2026-09-06T18:00:01Z"
}
Повторный запрос не должен создавать второй лид или контакт.
7. Пример cURL
curl -X POST "$CRM_BASE_URL/api/site/leads" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"externalId": "site-form-01J7K9A2M4Y5T6",
"lead": {
"leadSourceId": "7b33b18f-55bd-48a5-a8f0-e450a56dde47",
"quality": "WARM",
"message": "Перезвоните мне"
},
"contact": {
"firstName": "Ivan",
"lastName": "Ivanov",
"phone": "+79991234567",
"email": "ivan@example.com"
},
"marketing": {
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "crm-demo",
"landingUrl": "https://example.com/demo"
}
}'
8. Интерфейсы TypeScript
type LeadQuality = 'HOT' | 'WARM' | 'COOL' | 'COLD';
interface SiteLeadRequest {
externalId?: string;
lead: {
leadSourceId: string;
quality?: LeadQuality;
message?: string;
};
contact: {
firstName: string;
lastName?: string;
phone: string;
email?: string;
};
marketing?: {
occurredAt?: string;
visitorId?: string;
sessionId?: string;
source?: string;
channel?: string;
utmSource?: string;
utmMedium?: string;
utmCampaign?: string;
utmContent?: string;
utmTerm?: string;
gclid?: string;
fbclid?: string;
landingUrl?: string;
referrer?: string;
};
}
interface SiteLeadResponse {
leadId: string;
contactId: string;
phoneId: string;
emailId: string | null;
marketingEventId: string | null;
duplicate: boolean;
createdAt: string;
}
9. Пример на стороне сервера
export async function sendLeadToCrm(payload: SiteLeadRequest): Promise<SiteLeadResponse> {
const response = await fetch(`${process.env.CRM_BASE_URL}/api/site/leads`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CRM_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const body = await response.text();
throw new Error(`ReactiveCRM returned ${response.status}: ${body}`);
}
return response.json() as Promise<SiteLeadResponse>;
}
10. Ошибки
| HTTP | Причина | Действие интегратора |
|---|---|---|
400 | Ошибка формата, отсутствует обязательное поле, некорректный email/телефон/quality | Исправьте данные; не повторяйте автоматически без изменений |
401 | Токен отсутствует или недействителен | Обновите токен интеграции |
403 | Нет доступа к тенанту или операции | Проверьте пользователя и роль интеграции |
404 | leadSourceId не найден в текущем тенанте | Обновите ID источника в настройках сайта |
409 | externalId уже связан с несовместимым запросом | Проверьте генерацию ID и журнал интеграции |
5xx | Временная ошибка CRM | Повторите тот же запрос с тем же externalId |
Пример ошибки валидации:
{
"status": 400,
"error": "Bad Request",
"message": "contact.phone must not be blank",
"path": "/api/site/leads"
}
11. Идемпотентность
externalIdдолжен быть уникальным в рамках тенанта и источникаwebsite.- Генерируйте
externalIdдо первого запроса и сохраняйте его в заявке на сайте. - При тайм-ауте повторите запрос с тем же телом и тем же
externalId. - Не создавайте новый
externalIdпри повторе одной заявки. - Если
duplicate: true, заявка уже обработана и считается успешно доставленной.
12. Мини-инструкция для разработчика сайта
- При первом визите сохраните UTM-метки,
gclid,fbclid, URL посадочной страницы и referrer. - При отправке формы сгенерируйте стабильный
externalId. - Отправьте форму на бэкенд сайта.
- Бэкенд добавляет CRM-токен и вызывает
POST /api/site/leads. - При
201или200сduplicate: trueсчитайте заявку доставленной. - При тайм-ауте или
5xxповторите запрос с тем жеexternalId. - Никогда не отправляйте CRM-токен напрямую из браузера.
13. Что получает менеджер CRM
После успешного запроса в CRM появляется лид, который:
- имеет заданный источник
leadSourceId; - сохраняет сообщение посетителя;
- имеет начальную оценку качества, если сайт её передал;
- связан с контактом, у которого есть имя и основной телефон;
- связан с основным email, если он указан;
- сохраняет UTM-метрики и рекламные идентификаторы для аналитики.
Этого набора достаточно, чтобы менеджер увидел заявку, позвонил контакту и квалифицировал лид.
Reactive CRM