Добавить повторный лид
Scope:
crmКто может выполнять метод: пользователи с правом на чтение лидов, контактов, компаний и правом на создание лидов
Выберите инструмент для разработки с AI-агентом:
- используйте Битрикс24 Вайбкод, чтобы создать приложение для Битрикс24 по описанию задачи без знания языков программирования. Агент напишет код и разместит приложение на сервере без ручной настройки хостинга
- используйте MCP-сервер, чтобы разрабатывать интеграцию через REST API в своем проекте. Агент будет обращаться к официальной REST-документации
Когда клиент заполняет форму на сайте, его данные передаются в обработчик. Скрипт ищет в CRM совпадения по телефону или электронной почте среди лидов, контактов и компаний. Если совпадения найдены, лид помечается как повторный и привязывается к имеющейся записи. Такой подход помогает избежать дублей и повышает эффективность работы менеджеров.
В Битрикс24 должен быть включен режим работы с повторными лидами. Подробнее читайте в статье Повторные лиды и сделки.
Настройка состоит из двух этапов:
-
Подготавливаем поля и размещаем форму на странице.
-
Создаем файл-обработчик, который вызывает последовательно методы crm.duplicate.findbycomm, crm.lead.list, crm.lead.add.
1. Создаем веб-форму
Создаем HTML-форму с полями:
-
NAME— имя клиента, обязательное поле, -
LAST_NAME— фамилия, -
PHONE— телефон, -
EMAIL— электронная почта.
Форма передает данные методом POST в обработчик.
<form id="form_to_crm">
<input type="text" name="NAME" placeholder="Name" required>
<input type="text" name="LAST_NAME" placeholder="Last name">
<input type="text" name="PHONE" placeholder="Phone">
<input type="text" name="EMAIL" placeholder="E-mail">
<input type="submit" value="Submit">
</form>
<script>
document.getElementById('form_to_crm').addEventListener('submit', async (el) => {
el.preventDefault();
const formData = Object.fromEntries(new FormData(el.currentTarget).entries());
const response = await fetch('/form', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
});
const data = await response.json();
alert(data.message);
});
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('#form_to_crm').on( 'submit', function(el) {//event submit form
el.preventDefault();//the default action of the event will not be triggered
var formData = $(this).serialize();
$.ajax({
'method': 'POST',
'dataType': 'json',
'url': 'form.php', // файл для сохранения заполненных форм
'data': formData,
success: function(data){//success callback
alert(data.message);
}
});
});
});
</script>
<form id="form_to_crm">
<input type="text" name="NAME" placeholder="Name" required>
<input type="text" name="LAST_NAME" placeholder="Last name">
<input type="text" name="PHONE" placeholder="Phone">
<input type="text" name="EMAIL" placeholder="E-mail">
<input type="submit" value="Submit">
</form>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('#form_to_crm').on( 'submit', function(el) {//event submit form
el.preventDefault();//the default action of the event will not be triggered
var formData = $(this).serialize();
$.ajax({
'method': 'POST',
'dataType': 'json',
'url': '/form', // маршрут обработчика на Flask
'data': formData,
success: function(data){//success callback
alert(data.message);
}
});
});
});
</script>
<form id="form_to_crm">
<input type="text" name="NAME" placeholder="Name" required>
<input type="text" name="LAST_NAME" placeholder="Last name">
<input type="text" name="PHONE" placeholder="Phone">
<input type="text" name="EMAIL" placeholder="E-mail">
<input type="submit" value="Submit">
</form>
2. Создаем обработчик формы
Создаем обработчик. Он будет обрабатывать данные, проверять дубликаты и создавать лид.
Получаем данные из формы
Получаем и безопасно обрабатываем данные из полей NAME, LAST_NAME, PHONE, EMAIL, чтобы избежать XSS-атак.
const sName = String(req.body.NAME ?? '')
const sLastName = String(req.body.LAST_NAME ?? '')
const sPhone = String(req.body.PHONE ?? '')
const sEmail = String(req.body.EMAIL ?? '')
$sName = htmlspecialchars($_POST["NAME"]);
$sLastName = htmlspecialchars($_POST["LAST_NAME"]);
$sPhone = htmlspecialchars($_POST["PHONE"]);
$sEmail = htmlspecialchars($_POST["EMAIL"]);
s_name = request.form.get("NAME", "")
s_last_name = request.form.get("LAST_NAME", "")
s_phone = request.form.get("PHONE", "")
s_email = request.form.get("EMAIL", "")
Формируем массив $arFields с данными нового лида.
const arFields = {
TITLE: 'From the site: ' + [sName, sLastName].join(' '),
NAME: sName || 'Empty name',
LAST_NAME: sLastName,
PHONE: sPhone ? [{ VALUE: sPhone, VALUE_TYPE: 'HOME' }] : [],
EMAIL: sEmail ? [{ VALUE: sEmail, VALUE_TYPE: 'HOME' }] : [],
}
$arFields = [
'TITLE' => 'From the site: ' . implode(' ', [$sName, $sLastName]),
'NAME' => (!empty($sName)) ? $sName : 'Empty name',
'LAST_NAME' => $sLastName,
'PHONE' => (!empty($sPhone)) ? array(array('VALUE' => $sPhone, 'VALUE_TYPE' => 'HOME')) : array(),
'EMAIL' => (!empty($sEmail)) ? array(array('VALUE' => $sEmail, 'VALUE_TYPE' => 'HOME')) : array()
];
ar_fields = {
"TITLE": "From the site: " + " ".join([s_name, s_last_name]),
"NAME": s_name or "Empty name",
"LAST_NAME": s_last_name,
"PHONE": [{"VALUE": s_phone, "VALUE_TYPE": "HOME"}] if s_phone else [],
"EMAIL": [{"VALUE": s_email, "VALUE_TYPE": "HOME"}] if s_email else [],
}
Заголовок лида формируем как From the site: Имя Фамилия.
Система хранит телефон и электронную почту как массивы объектов crm_multifield, поэтому формируем массивы PHONE и EMAIL с помощью значений $sPhone и $sEmail.
-
В поля
VALUEзаписываем$sPhoneи$sEmail. -
В поля
VALUE_TYPEпередаем типы, например,HOME.
Если в переменных $sPhone и $sEmail нет значений, указываем пустые массивы.
Ищем дубликаты лидов
Чтобы найти повторяющиеся лиды по телефону и электронной почте, используем метод crm.duplicate.findbycomm дважды. В него нужно передать следующие данные:
-
entity_type— тип объекта. ПередаемLEAD— лид. -
type— тип коммуникации. При первом вызове указываемPHONE, при втором —EMAIL. -
values— массив значений для поиска. Передаем значение телефона$sPhone, который получили из формы.
Поиск по телефону, "type" => "PHONE".
if (sPhone) {
const resultDuplicate = await $b24.actions.v2.call.make({
method: 'crm.duplicate.findbycomm',
params: { entity_type: 'LEAD', type: 'PHONE', values: [sPhone] },
requestId: 'dup-phone'
})
const found = resultDuplicate.getData()?.result?.LEAD
if (found) arLeadDuplicate = arLeadDuplicate.concat(found)
}
if (!empty($sPhone)) {
$result = $sb->getCRMScope()->duplicate()->findByPhone(
[$sPhone],
\Bitrix24\SDK\Services\CRM\Duplicates\Service\EntityType::Lead
)->getCoreResponse()->getResponseData()->getResult();
if (!empty($result['LEAD'])) {
$arLeadDuplicate = array_merge($arLeadDuplicate, $result['LEAD']);
}
}
if s_phone:
result_duplicate = client.crm.duplicate.findbycomm(
type="PHONE", values=[s_phone], entity_type="LEAD",
).result
if result_duplicate.get("LEAD"):
ar_lead_duplicate += result_duplicate["LEAD"]
Поиск дубликатов по электронной почте, "type" => "EMAIL".
if (sEmail) {
const resultDuplicate = await $b24.actions.v2.call.make({
method: 'crm.duplicate.findbycomm',
params: { entity_type: 'LEAD', type: 'EMAIL', values: [sEmail] },
requestId: 'dup-email'
})
const found = resultDuplicate.getData()?.result?.LEAD
if (found) arLeadDuplicate = arLeadDuplicate.concat(found)
}
if (!empty($sEmail)) {
$result = $sb->getCRMScope()->duplicate()->findByEmail(
[$sEmail],
\Bitrix24\SDK\Services\CRM\Duplicates\Service\EntityType::Lead
)->getCoreResponse()->getResponseData()->getResult();
if (!empty($result['LEAD'])) {
$arLeadDuplicate = array_merge($arLeadDuplicate, $result['LEAD']);
}
}
if s_email:
result_duplicate = client.crm.duplicate.findbycomm(
type="EMAIL", values=[s_email], entity_type="LEAD",
).result
if result_duplicate.get("LEAD"):
ar_lead_duplicate += result_duplicate["LEAD"]
Идентификаторы найденных дубликатов объединяем в массиве $arLeadDuplicate.
Обрабатываем дубликаты
Если дубликаты найдены, вызываем метод crm.lead.list.
-
Применяем фильтр по идентификатору и статусу
CONVERTED. -
Выбираем поля:
ID,COMPANY_ID,CONTACT_ID. -
Сохраняем результат в массиве
$arDuplicateLead. -
Заполняем поля
COMPANY_IDиCONTACT_IDв массиве$arFieldsзначениями из$arDuplicateLead.
if (arLeadDuplicate.length) {
const duplicateLead = await $b24.actions.v2.callList.make({
method: 'crm.lead.list',
params: {
filter: { '=ID': arLeadDuplicate, STATUS_ID: 'CONVERTED' },
select: ['ID', 'COMPANY_ID', 'CONTACT_ID']
},
requestId: 'dup-lead-list'
})
const arDuplicateLead = duplicateLead.getData()?.result ?? []
const company = arDuplicateLead.map(r => r.COMPANY_ID).find(v => v > 0)
const contact = arDuplicateLead.map(r => r.CONTACT_ID).find(v => v > 0)
if (company) arFields.COMPANY_ID = company
if (contact) arFields.CONTACT_ID = contact
}
if (!empty($arLeadDuplicate)) {
$arDuplicateLead = [];
foreach ($sb->getCRMScope()->lead()->batch->list(
order: [],
filter: ['=ID' => $arLeadDuplicate, 'STATUS_ID' => 'CONVERTED'],
select: ['ID', 'COMPANY_ID', 'CONTACT_ID']
) as $lead) {
$arDuplicateLead[] = $lead;
}
foreach ($arDuplicateLead as $lead) {
if ($lead->COMPANY_ID > 0 && empty($arFields['COMPANY_ID'])) {
$arFields['COMPANY_ID'] = $lead->COMPANY_ID;
}
if ($lead->CONTACT_ID > 0 && empty($arFields['CONTACT_ID'])) {
$arFields['CONTACT_ID'] = $lead->CONTACT_ID;
}
}
}
if ar_lead_duplicate:
ar_duplicate_lead = client.crm.lead.list(
filter={"=ID": ar_lead_duplicate, "STATUS_ID": "CONVERTED"},
select=["ID", "COMPANY_ID", "CONTACT_ID"],
).as_list().result
company = next((r["COMPANY_ID"] for r in ar_duplicate_lead if int(r["COMPANY_ID"] or 0) > 0), None)
contact = next((r["CONTACT_ID"] for r in ar_duplicate_lead if int(r["CONTACT_ID"] or 0) > 0), None)
if company:
ar_fields["COMPANY_ID"] = company
if contact:
ar_fields["CONTACT_ID"] = contact
Добавляем новый лид
Чтобы добавить лид, используем метод crm.lead.add. В него передаем массив $arFields.
Проверьте, какие обязательные поля настроены для лидов в вашем Битрикс24. Все обязательные поля нужно передать в метод crm.lead.add.
await $b24.actions.v2.call.make({
method: 'crm.lead.add',
params: { fields: arFields },
requestId: 'repeat-lead-add'
})
$sb->getCRMScope()->lead()->add($arFields);
client.crm.lead.add(fields=ar_fields)
Если лид создан успешно, метод вернет его идентификатор. Если вы получили ошибку error, изучите описание возможных ошибок в документации метода crm.lead.add.
{
"result": 3289,
}
Полный пример кода обработчика
import express from 'express'
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/'
const app = express()
app.use(express.json())
// Обработчик принимает данные формы по маршруту /form
app.post('/form', async (req, res) => {
const sName = String(req.body.NAME ?? '')
const sLastName = String(req.body.LAST_NAME ?? '')
const sPhone = String(req.body.PHONE ?? '')
const sEmail = String(req.body.EMAIL ?? '')
const arFields = {
TITLE: sName || 'Empty name',
LAST_NAME: sLastName,
PHONE: sPhone ? [{ VALUE: sPhone, VALUE_TYPE: 'HOME' }] : [],
EMAIL: sEmail ? [{ VALUE: sEmail, VALUE_TYPE: 'HOME' }] : [],
}
let arLeadDuplicate = []
if (sPhone) { // поиск дубликатов по телефону
const r = await $b24.actions.v2.call.make({
method: 'crm.duplicate.findbycomm',
params: { entity_type: 'LEAD', type: 'PHONE', values: [sPhone] },
requestId: 'dup-phone'
})
const found = r.getData()?.result?.LEAD
if (found) arLeadDuplicate = arLeadDuplicate.concat(found)
}
if (sEmail) { // поиск дубликатов по email
const r = await $b24.actions.v2.call.make({
method: 'crm.duplicate.findbycomm',
params: { entity_type: 'LEAD', type: 'EMAIL', values: [sEmail] },
requestId: 'dup-email'
})
const found = r.getData()?.result?.LEAD
if (found) arLeadDuplicate = arLeadDuplicate.concat(found)
}
if (arLeadDuplicate.length) { // получение дубликата лида с полями связанных контакта и компании
const duplicateLead = await $b24.actions.v2.callList.make({
method: 'crm.lead.list',
params: {
filter: { '=ID': arLeadDuplicate, STATUS_ID: 'CONVERTED' },
select: ['ID', 'COMPANY_ID', 'CONTACT_ID']
},
requestId: 'dup-lead-list'
})
const arDuplicateLead = duplicateLead.getData()?.result ?? []
const company = arDuplicateLead.map(r => r.COMPANY_ID).find(v => v > 0)
const contact = arDuplicateLead.map(r => r.CONTACT_ID).find(v => v > 0)
if (company) arFields.COMPANY_ID = company
if (contact) arFields.CONTACT_ID = contact
}
const result = await $b24.actions.v2.call.make({ // создание повторного лида
method: 'crm.lead.add',
params: { fields: arFields },
requestId: 'repeat-lead-add'
})
if (result.isSuccess && result.getData()?.result) {
res.json({ message: 'Lead add' })
} else {
res.json({ message: 'Lead not added: ' + result.getErrorMessages().join('; ') })
}
})
app.listen(3000)
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Bitrix24\SDK\Services\CRM\Duplicates\Service\EntityType;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('b24');
$log->pushHandler(new StreamHandler('php://stdout'));
$sb = (new ServiceBuilderFactory(new EventDispatcher(), $log))
->initFromWebhook('https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/');
$sName = htmlspecialchars($_POST["NAME"]);
$sLastName = htmlspecialchars($_POST["LAST_NAME"]);
$sPhone = htmlspecialchars($_POST["PHONE"]);
$sEmail = htmlspecialchars($_POST["EMAIL"]);
$arFields = [
'TITLE' => (!empty($sName)) ? $sName : 'Empty name',
'LAST_NAME' => $sLastName,
'PHONE' => (!empty($sPhone)) ? array(array('VALUE' => $sPhone, 'VALUE_TYPE' => 'HOME')) : array(),
'EMAIL' => (!empty($sEmail)) ? array(array('VALUE' => $sEmail, 'VALUE_TYPE' => 'HOME')) : array()
];
$arLeadDuplicate = [];
if (!empty($sPhone)) { // поиск дубликатов по телефону
$r = $sb->getCRMScope()->duplicate()->findByPhone([$sPhone], EntityType::Lead)
->getCoreResponse()->getResponseData()->getResult();
if (!empty($r['LEAD'])) {
$arLeadDuplicate = array_merge($arLeadDuplicate, $r['LEAD']);
}
}
if (!empty($sEmail)) { // поиск дубликатов по email
$r = $sb->getCRMScope()->duplicate()->findByEmail([$sEmail], EntityType::Lead)
->getCoreResponse()->getResponseData()->getResult();
if (!empty($r['LEAD'])) {
$arLeadDuplicate = array_merge($arLeadDuplicate, $r['LEAD']);
}
}
if (!empty($arLeadDuplicate)) { // получение дубликата лида с полями связанных контакта и компании
foreach ($sb->getCRMScope()->lead()->batch->list(
order: [],
filter: ['=ID' => $arLeadDuplicate, 'STATUS_ID' => 'CONVERTED'],
select: ['ID', 'COMPANY_ID', 'CONTACT_ID']
) as $lead) {
if ($lead->COMPANY_ID > 0 && empty($arFields['COMPANY_ID'])) {
$arFields['COMPANY_ID'] = $lead->COMPANY_ID;
}
if ($lead->CONTACT_ID > 0 && empty($arFields['CONTACT_ID'])) {
$arFields['CONTACT_ID'] = $lead->CONTACT_ID;
}
}
}
try {
$sb->getCRMScope()->lead()->add($arFields); // создание повторного лида
echo json_encode(['message' => 'Lead add']);
} catch (\Throwable $e) {
echo json_encode(['message' => 'Lead not added: ' . $e->getMessage()]);
}
# pip install b24pysdk
from flask import Flask, request, jsonify
from b24pysdk import BitrixWebhook, Client
app = Flask(__name__)
client = Client(BitrixWebhook(
domain="your-domain.bitrix24.ru",
webhook_token="USER_ID/TOKEN", # только user_id/token, без https://
))
@app.route("/form", methods=["POST"])
def handle_form():
s_name = request.form.get("NAME", "")
s_last_name = request.form.get("LAST_NAME", "")
s_phone = request.form.get("PHONE", "")
s_email = request.form.get("EMAIL", "")
ar_fields = {
"TITLE": s_name or "Empty name",
"LAST_NAME": s_last_name,
"PHONE": [{"VALUE": s_phone, "VALUE_TYPE": "HOME"}] if s_phone else [],
"EMAIL": [{"VALUE": s_email, "VALUE_TYPE": "HOME"}] if s_email else [],
}
ar_lead_duplicate = []
if s_phone: # поиск дубликатов по телефону
r = client.crm.duplicate.findbycomm(type="PHONE", values=[s_phone], entity_type="LEAD").result
if r.get("LEAD"):
ar_lead_duplicate += r["LEAD"]
if s_email: # поиск дубликатов по email
r = client.crm.duplicate.findbycomm(type="EMAIL", values=[s_email], entity_type="LEAD").result
if r.get("LEAD"):
ar_lead_duplicate += r["LEAD"]
if ar_lead_duplicate: # получение дубликата лида с полями связанных контакта и компании
ar_duplicate_lead = client.crm.lead.list(
filter={"=ID": ar_lead_duplicate, "STATUS_ID": "CONVERTED"},
select=["ID", "COMPANY_ID", "CONTACT_ID"],
).as_list().result
company = next((r["COMPANY_ID"] for r in ar_duplicate_lead if int(r["COMPANY_ID"] or 0) > 0), None)
contact = next((r["CONTACT_ID"] for r in ar_duplicate_lead if int(r["CONTACT_ID"] or 0) > 0), None)
if company:
ar_fields["COMPANY_ID"] = company
if contact:
ar_fields["CONTACT_ID"] = contact
try:
client.crm.lead.add(fields=ar_fields) # создание повторного лида
return jsonify({"message": "Lead add"})
except Exception as e:
return jsonify({"message": f"Lead not added: {e}"})