How to Use Sales Intelligence When Creating a Lead
Scope:
crmWho can execute the methods: to complete the entire scenario, the user needs permissions to add and edit leads
- crm.item.add — a user with permission to add leads
- crm.tracking.trace.add — a user with permission to edit the created lead
Choose a tool for developing with an AI agent:
- use Alaio Vibecode to build an app for Bitrix24 from a task description without knowing any programming language. The agent writes the code and deploys the app to a server, with no manual hosting setup
- use the MCP server to develop a REST API integration in your own project. The agent refers to the official REST documentation
Sales Intelligence shows the customer's acquisition source. When a customer fills out a form on a site, you can pass the name, phone number, and advertising channel data with the visit path to the lead card.
Visit data is collected by the Sales Intelligence script that Bitrix24 generates for your site. The script returns this data as a trace — a JSON string with the referral source and the visited pages. When the form is submitted, the code retrieves the trace and links the created lead to it.
The scenario consists of four steps.
- Add a feedback form with a hidden
TRACEfield to the page - Retrieve the visitor's trace with the
b24Tracker.guest.getTrace()function when the form is submitted - Create a lead with the crm.item.add method
- Link the lead to the trace with the crm.tracking.trace.add method
The crm.tracking.trace.add method returns the trace identifier, and Sales Intelligence data appears in the lead card. If the trace could not be retrieved, the backend still creates the lead, but without a link to the source.
The form on the website is public, so REST calls are performed on the server side rather than in the browser: a webhook with CRM permissions must not be exposed in client-side code. The browser only collects the form data and the trace, then sends them to your backend via a standard POST request. The backend calls Bitrix24 methods via an SDK:
Before You Start
- an inbound webhook with the
crmscope, created on behalf of a user with permissions to add and edit leads - the Sales Intelligence script generated by Bitrix24 on all website pages where you need to track the visitor's path, including the page containing the form. After the script loads, the
b24Tracker.guest.getTrace()function is available on the page - a server for the form handler in JS, PHP, or Python
Environment requirements for the examples:
- JS — Node.js 18, 20, or 22 and higher. B24JsSDK is an ES module: save the code in a
.mjsfile or add"type": "module"topackage.json - PHP — PHP 8.4 or 8.5 for B24PhpSDK 3.x
- Python — Python 3.9 or newer
1. Add a Form to the Website
Add fields to the feedback form:
NAME— customer first nameLAST_NAME— customer last namePHONE— customer phone numberTRACE— Sales Intelligence trace, a hidden form field
The form sends data to the backend via a standard POST request, so its markup is the same for all languages:
How to Use Examples in Documentation
<form id="feedback-form" method="post" action="/">
<input type="hidden" id="FORM_TRACE" name="TRACE">
<input type="text" name="NAME" required>
<input type="text" name="LAST_NAME" required>
<input type="text" name="PHONE" required>
<input type="submit" name="SAVE" value="Send">
</form>
The user does not see the hidden field, but its value is sent along with the rest of the form data.
2. Retrieve the Trace When the Form Is Submitted
The b24Tracker.guest.getTrace() function returns the trace — a JSON string — and immediately clears the stored history of the visitor's previous visits. That is why we call it once, when the form is submitted, rather than when the page loads. If the visitor leaves the page without submitting the form, the history is retained for the next visit.
Write the trace into the hidden TRACE field. This is client-side code—it runs in the browser on the page containing the form:
<!-- The Bitrix24 Sales Intelligence script must be installed on the page -->
<script>
document.getElementById('feedback-form').addEventListener('submit', function() {
var traceInput = document.getElementById('FORM_TRACE');
var tracker = window.b24Tracker && window.b24Tracker.guest;
if (tracker && typeof tracker.getTrace === 'function') {
traceInput.value = tracker.getTrace();
}
});
</script>
If the function is not available, the form is submitted with an empty TRACE field, and the backend creates the lead without a link to Sales Intelligence.
If the Sales Intelligence script is not installed on the site or has not loaded by the time the form is submitted, the code does not retrieve the trace. Check that the Sales Intelligence script is included on the page containing the form.
3. Create a Lead
To create a lead, use the universal crm.item.add method. Pass the value 1 — the lead object type identifier — in the entityTypeId parameter.
Pass the following parameters in fields:
title— lead namename— customer first namelastName— customer last namefm— phone number in the CRM multi-field format
Pass the fm field as an array because the phone number in the CRM is stored as a crm_multifield type multi-field. For the phone number, specify:
typeId—PHONEmulti-field typevalueType— value type, for exampleWORKvalue— phone number
The crm.item.add method creates a lead and returns no error in two cases:
- the phone number is passed as a string instead of an array or without
typeId— the lead is created without a phone number - fields configured as required in the lead card are empty — the method checks them only if the "Check for required custom fields" option is enabled in the CRM settings
Pass fm in the format above and fill in the required lead fields in fields yourself.
// npm install @bitrix24/b24jssdk
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl('https://your-domain.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/')
// name, lastName, phone come from the form data (req.body)
const leadResponse = await $b24.actions.v2.call.make({
method: 'crm.item.add',
params: {
entityTypeId: 1,
fields: {
title: `Feedback page: ${name} ${lastName}`,
name: name,
lastName: lastName,
fm: [
{ typeId: 'PHONE', valueType: 'WORK', value: phone },
],
},
},
requestId: 'lead-add',
})
if (!leadResponse.isSuccess) {
throw new Error(leadResponse.getErrorMessages().join('; '))
}
const leadId = leadResponse.getData().result.item.id
# pip install b24pysdk
from b24pysdk import Client, BitrixWebhook
client = Client(BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token="1/xxxxxxxxxxxxxxxx",
))
# name, last_name, phone come from the form data
bitrix_response = client.crm.item.add(
fields={
"title": f"Feedback page: {name} {last_name}",
"name": name,
"lastName": last_name,
"fm": [
{"typeId": "PHONE", "valueType": "WORK", "value": phone},
],
},
entity_type_id=1,
).response
lead_id = bitrix_response.result["item"]["id"]
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
$b24 = ServiceBuilderFactory::createServiceBuilderFromWebhook(
'https://your-domain.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/'
);
// $name, $lastName, $phone come from the form data ($_POST)
$leadId = $b24->getCRMScope()->item()->add(1, [
'title' => 'Feedback page: ' . $name . ' ' . $lastName,
'name' => $name,
'lastName' => $lastName,
'fm' => [
['typeId' => 'PHONE', 'valueType' => 'WORK', 'value' => $phone],
],
])->item()->id;
The crm.item.add method returns the lead identifier in the result.item.id field. Retain this identifier for use in the ENTITIES parameter in step 4.
An abbreviated example of the response is shown below. For the full response format, see the crm.item.add method description.
{
"result": {
"item": {
"id": 123
}
}
}
4. Link the Lead to the Trace
After creating a lead, call the crm.tracking.trace.add method because TRACE cannot be passed directly to crm.item.add.
You can pass UTM parameters to crm.item.add: utmSource, utmMedium, utmCampaign, utmContent, utmTerm. These retain advertising tags in the lead but do not replace the full trace.
Pass the following parameters to the crm.tracking.trace.add method:
TRACE— the trace JSON string from the hidden form fieldENTITIES— an array of objects to be linked to the trace. For a lead, specifyTYPEwith the valueLEADandIDfrom theresult.item.idfield of the crm.item.add response
if (trace) {
const traceResponse = await $b24.actions.v2.call.make({
method: 'crm.tracking.trace.add',
params: {
TRACE: trace,
ENTITIES: [
{ TYPE: 'LEAD', ID: leadId },
],
},
requestId: 'trace-add',
})
if (!traceResponse.isSuccess) {
throw new Error(traceResponse.getErrorMessages().join('; '))
}
const traceId = traceResponse.getData().result
}
if trace:
trace_id = client.crm.tracking.trace.add(
trace=trace,
entities=[
{"TYPE": "LEAD", "ID": lead_id},
],
).response.result
if ($trace !== '') {
// crm.tracking.* is not among the typed services — calling directly via the core
$traceId = $b24->core->call('crm.tracking.trace.add', [
'TRACE' => $trace,
'ENTITIES' => [
['TYPE' => 'LEAD', 'ID' => $leadId],
],
])->getResponseData()->getResult()[0];
}
If TRACE is empty, the code skips this step, and the lead remains without a link to Sales Intelligence.
The crm.tracking.trace.add method returns the identifier of the created trace in the result field.
{
"result": 341
}
Full Code Example
In the examples below, the backend serves an HTML page with a form and processes its submission. Set the URL variable to your Bitrix24 webhook address and start the server:
- JS — save the code in the
app.mjsfile and runnode app.mjs - Python — save the code in the
app.pyfile and runpython app.py - PHP — save the code in the
index.phpfile and runphp -S localhost:3000 index.php
The form opens at http://localhost:3000. After submission, the page shows whether the lead was created and whether it was linked to the trace.
// npm install express @bitrix24/b24jssdk
import express from 'express'
import { B24Hook } from '@bitrix24/b24jssdk'
const WEBHOOK = 'https://your-domain.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/'
const app = express()
app.use(express.urlencoded({ extended: false }))
const PAGE = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Feedback</title>
</head>
<body>
<h1>Feedback</h1>
<p id="message" aria-live="polite">__MESSAGE__</p>
<form id="feedback-form" method="post" action="/">
<input type="hidden" id="FORM_TRACE" name="TRACE">
<label>First Name <input type="text" name="NAME" required></label>
<label>Last Name <input type="text" name="LAST_NAME" required></label>
<label>Phone <input type="tel" name="PHONE" required></label>
<button type="submit">Send</button>
</form>
<!-- The Bitrix24 Sales Intelligence script must be installed on the page -->
<script>
document.getElementById('feedback-form').addEventListener('submit', function() {
var traceInput = document.getElementById('FORM_TRACE');
var tracker = window.b24Tracker && window.b24Tracker.guest;
if (tracker && typeof tracker.getTrace === 'function') {
traceInput.value = tracker.getTrace();
}
});
</script>
</body>
</html>`
const HTML_ESCAPES = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
}
const escapeHtml = (value) => String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPES[char])
const formPage = (message = '') => PAGE.replace('__MESSAGE__', escapeHtml(message))
const formValue = (value) => (typeof value === 'string' ? value.trim() : '')
app.get('/', (req, res) => res.send(formPage()))
app.post('/', async (req, res) => {
const name = formValue(req.body.NAME)
const lastName = formValue(req.body.LAST_NAME)
const phone = formValue(req.body.PHONE)
const trace = formValue(req.body.TRACE)
if (!name || !lastName || !phone) {
return res.send(formPage('Please fill in your first name, last name, and phone number'))
}
const $b24 = B24Hook.fromWebhookUrl(WEBHOOK)
let leadId = 0
try {
const leadResponse = await $b24.actions.v2.call.make({
method: 'crm.item.add',
params: {
entityTypeId: 1,
fields: {
title: `Feedback page: ${name} ${lastName}`,
name: name,
lastName: lastName,
fm: [{ typeId: 'PHONE', valueType: 'WORK', value: phone }],
},
},
requestId: 'lead-add',
})
if (!leadResponse.isSuccess) {
return res.send(formPage('Lead not created: ' + leadResponse.getErrorMessages().join('; ')))
}
leadId = leadResponse.getData().result.item.id
if (!trace) {
return res.send(formPage('Lead ' + leadId + ' created without a trace'))
}
const traceResponse = await $b24.actions.v2.call.make({
method: 'crm.tracking.trace.add',
params: { TRACE: trace, ENTITIES: [{ TYPE: 'LEAD', ID: leadId }] },
requestId: 'trace-add',
})
if (!traceResponse.isSuccess) {
return res.send(formPage(
'Lead ' + leadId + ' created, but the trace is not linked: '
+ traceResponse.getErrorMessages().join('; ')
))
}
const traceId = traceResponse.getData().result
return res.send(formPage('Lead ' + leadId + ' created and linked to trace ' + traceId))
} catch (error) {
if (leadId > 0) {
return res.send(formPage('Lead ' + leadId + ' created, but the trace is not linked: ' + error.message))
}
return res.send(formPage('Lead not created: ' + error.message))
} finally {
$b24.destroy()
}
})
app.listen(3000, () => console.log('http://localhost:3000'))
# pip install b24pysdk flask
import html
from flask import Flask, request
from b24pysdk import Client, BitrixWebhook
WEBHOOK_DOMAIN = "your-domain.bitrix24.com"
WEBHOOK_TOKEN = "1/xxxxxxxxxxxxxxxx"
app = Flask(__name__)
PAGE = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Feedback</title>
</head>
<body>
<h1>Feedback</h1>
<p id="message" aria-live="polite">__MESSAGE__</p>
<form id="feedback-form" method="post" action="/">
<input type="hidden" id="FORM_TRACE" name="TRACE">
<label>First Name <input type="text" name="NAME" required></label>
<label>Last Name <input type="text" name="LAST_NAME" required></label>
<label>Phone <input type="tel" name="PHONE" required></label>
<button type="submit">Send</button>
</form>
<!-- The Bitrix24 Sales Intelligence script must be installed on the page -->
<script>
document.getElementById('feedback-form').addEventListener('submit', function() {
var traceInput = document.getElementById('FORM_TRACE');
var tracker = window.b24Tracker && window.b24Tracker.guest;
if (tracker && typeof tracker.getTrace === 'function') {
traceInput.value = tracker.getTrace();
}
});
</script>
</body>
</html>"""
def form_page(message: str = "") -> str:
return PAGE.replace("__MESSAGE__", html.escape(message))
@app.get("/")
def index():
return form_page()
@app.post("/")
def submit():
name = request.form.get("NAME", "").strip()
last_name = request.form.get("LAST_NAME", "").strip()
phone = request.form.get("PHONE", "").strip()
trace = request.form.get("TRACE", "").strip()
if not name or not last_name or not phone:
return form_page("Please fill in your first name, last name, and phone number")
client = Client(BitrixWebhook(domain=WEBHOOK_DOMAIN, webhook_token=WEBHOOK_TOKEN))
try:
lead_id = client.crm.item.add(
fields={
"title": f"Feedback page: {name} {last_name}",
"name": name,
"lastName": last_name,
"fm": [{"typeId": "PHONE", "valueType": "WORK", "value": phone}],
},
entity_type_id=1,
).response.result["item"]["id"]
except Exception as error:
return form_page(f"Lead not created: {error}")
if not trace:
return form_page(f"Lead {lead_id} created without a trace")
try:
trace_id = client.crm.tracking.trace.add(
trace=trace,
entities=[{"TYPE": "LEAD", "ID": lead_id}],
).response.result
except Exception as error:
return form_page(f"Lead {lead_id} created, but the trace is not linked: {error}")
return form_page(f"Lead {lead_id} created and linked to trace {trace_id}")
if __name__ == "__main__":
app.run(port=3000)
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$formValue = static fn (string $key): string => is_string($_POST[$key] ?? null) ? trim($_POST[$key]) : '';
$name = $formValue('NAME');
$lastName = $formValue('LAST_NAME');
$phone = $formValue('PHONE');
$trace = $formValue('TRACE');
if ($name === '' || $lastName === '' || $phone === '') {
$message = 'Please fill in your first name, last name, and phone number';
} else {
$b24 = ServiceBuilderFactory::createServiceBuilderFromWebhook(
'https://your-domain.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/'
);
$leadId = 0;
try {
$leadId = $b24->getCRMScope()->item()->add(1, [
'title' => 'Feedback page: ' . $name . ' ' . $lastName,
'name' => $name,
'lastName' => $lastName,
'fm' => [
['typeId' => 'PHONE', 'valueType' => 'WORK', 'value' => $phone],
],
])->item()->id;
if ($trace === '') {
$message = "Lead $leadId created without a trace";
} else {
$traceId = $b24->core->call('crm.tracking.trace.add', [
'TRACE' => $trace,
'ENTITIES' => [
['TYPE' => 'LEAD', 'ID' => $leadId],
],
])->getResponseData()->getResult()[0];
$message = "Lead $leadId created and linked to trace $traceId";
}
} catch (\Throwable $e) {
$message = $leadId > 0
? "Lead $leadId created, but the trace is not linked: " . $e->getMessage()
: 'Lead not created: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Feedback</title>
</head>
<body>
<h1>Feedback</h1>
<p id="message" aria-live="polite"><?= htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></p>
<form id="feedback-form" method="post" action="">
<input type="hidden" id="FORM_TRACE" name="TRACE">
<label>First Name <input type="text" name="NAME" required></label>
<label>Last Name <input type="text" name="LAST_NAME" required></label>
<label>Phone <input type="tel" name="PHONE" required></label>
<button type="submit">Send</button>
</form>
<!-- The Bitrix24 Sales Intelligence script must be installed on the page -->
<script>
document.getElementById('feedback-form').addEventListener('submit', function() {
var traceInput = document.getElementById('FORM_TRACE');
var tracker = window.b24Tracker && window.b24Tracker.guest;
if (tracker && typeof tracker.getTrace === 'function') {
traceInput.value = tracker.getTrace();
}
});
</script>
</body>
</html>
Check the Result
The scenario is complete if, after the form is submitted, the page shows the message "Lead N created and linked to trace M": the crm.item.add method returned the lead identifier, and the crm.tracking.trace.add method returned the trace identifier.
- Open the lead with this identifier in the CRM and check the first name, last name, and phone number
- Check the "UTM parameters" field. If the visitor came to the site via a link with tags, Bitrix24 fills in the field with values from the trace. Tags already retained in the lead are not overwritten by the trace. Via REST, the crm.item.get method returns these values in the
utmSource,utmMedium, andutmCampaignfields - Open the "Sales Intelligence" field — Bitrix24 shows the trace data in it. If the field is not in the card, add it via "Select field"
If the page shows "Lead N created without a trace", the browser submitted the form with an empty TRACE field. Check that the Sales Intelligence script is included on the page containing the form.
Errors and Troubleshooting
If a method returns an error, check the request data and user permissions. The crm.item.add method returns the error code in the error field. For errors related to the TRACE and ENTITIES parameters and permissions, crm.tracking.trace.add returns the ERROR_CORE code and the specific cause in error_description.
|
Error Code or Text |
Cause and Action |
|
|
The user does not have permission to add a lead. Check the permissions of the user who created the webhook and repeat step 3 |
|
|
A required lead field is empty. The error can occur if the check of required fields is enabled in the CRM settings. Pass the field in |
|
|
The trace is missing or empty. Check that the Sales Intelligence script is loaded and the hidden |
|
|
|
|
|
An unsupported object type was passed. For a lead, specify |
|
|
An empty, non-numeric, or non-positive identifier was passed. Pass |
|
|
The user does not have permission to update the lead. Check the user's permissions for this lead and repeat step 4 |
In the last message, LEAD and 123 are examples. The method substitutes the actual object type and identifier.
The calls are made sequentially and are not combined into a transaction. If linking the trace fails, the lead is already retained in the CRM. Do not submit the form again — this creates a second lead. Repeat only step 4 with the identifier of the created lead.
For retries, retain the lead identifier and the original TRACE string on the server until the trace is successfully linked. Calling b24Tracker.guest.getTrace() again does not return the cleared visit history. The examples on this page do not implement this storage.
If a call was interrupted by a network error, Bitrix24 may have already executed the method. Before repeating the step, check whether the lead appeared in the CRM.
If the lead is created but has no phone number, check the fm structure against step 3: in this case, the crm.item.add method returns no error.
What to Consider
- submitting the form again creates a new lead
- protect the public form from automated submissions, for example with a CAPTCHA and a request rate limit