Add Lead with Files via Web Form
Scope:
crmWho can execute the method: users with permission to create leads in CRM
If you are developing integrations for Bitrix24 using AI tools (Codex, Claude Code, Cursor), connect the MCP server so that the assistant can utilize the official REST documentation.
You can place a form on your website to collect data from potential clients. When a client fills out the form and attaches files, their data will be sent to the CRM, allowing you to process the request.
Setting up the form consists of two steps.
-
Place the form on an HTML page. It will send data to the handler.
-
Create a file to process the data. The handler will receive and prepare the data, and then create a lead using the crm.lead.add method.
1. Creating the Web Form
In Bitrix24, a contact and a company can be automatically created from a lead. To make the form suitable for different scenarios, we will make it universal. For a contact, a first name and last name must be specified, and for a company, a name is required. We will create a web form on a website page with the following fields:
-
NAME— First Name, required, -
LAST_NAME— Last Name, -
COMPANY_TITLE— Company Name, -
EMAIL— Email, -
PHONE— Phone.
To allow the customer to upload files, we will add the following fields to the form:
-
FILE— for a single file, -
FILES— for adding multiple files.
Upon submission, the form passes the data to the handler.
<form id="form_to_crm" enctype="multipart/form-data">
<!-- First Name (required field) -->
<input type="text" name="NAME" placeholder="First Name" required>
<!-- Last Name -->
<input type="text" name="LAST_NAME" placeholder="Last Name">
<!-- Company Name -->
<input type="text" name="COMPANY_TITLE" placeholder="Company Name">
<!-- Email -->
<input type="text" name="EMAIL" placeholder="Email">
<!-- Phone -->
<input type="text" name="PHONE" placeholder="Phone">
<!-- Single file field -->
<input type="file" name="FILE">
<!-- Multiple files field -->
<input type="file" name="FILES" multiple>
<!-- Submit button -->
<input type="submit" value="Submit">
</form>
<script>
document.getElementById('form_to_crm').addEventListener('submit', async (el) => {
el.preventDefault();
// FormData will collect text fields and files itself (multipart/form-data)
const formData = new FormData(el.currentTarget);
// Do not specify Content-Type — the browser will set multipart with boundary
const response = await fetch('/form', { method: 'POST', body: formData });
const data = await response.json();
alert(data.message);
});
</script>
<form id="form_to_crm" method="POST" action="form.php" enctype="multipart/form-data">
<!-- First Name (required field) -->
<input type="text" name="NAME" placeholder="First Name" required>
<!-- Last Name -->
<input type="text" name="LAST_NAME" placeholder="Last Name">
<!-- Company Name -->
<input type="text" name="COMPANY_TITLE" placeholder="Company Name">
<!-- Email -->
<input type="text" name="EMAIL" placeholder="Email">
<!-- Phone -->
<input type="text" name="PHONE" placeholder="Phone">
<!-- Single file field -->
<input type="file" name="FILE">
<!-- Multiple files field -->
<input type="file" name="FILES" multiple>
<!-- Submit button -->
<input type="submit" value="Submit">
</form>
<!-- Include jQuery for the AJAX request -->
<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) {
el.preventDefault();
var formData = new FormData(this); // Collect form data including files
$.ajax({
method: 'POST',
url: 'form.php',
data: formData,
processData: false,
contentType: false,
dataType: 'json',
success: function(data) {
alert(data.message);
},
error: function() {
alert('Error during form submission');
}
});
});
});
</script>
<form id="form_to_crm" enctype="multipart/form-data">
<!-- First Name (required field) -->
<input type="text" name="NAME" placeholder="First Name" required>
<!-- Last Name -->
<input type="text" name="LAST_NAME" placeholder="Last Name">
<!-- Company Name -->
<input type="text" name="COMPANY_TITLE" placeholder="Company Name">
<!-- Email -->
<input type="text" name="EMAIL" placeholder="Email">
<!-- Phone -->
<input type="text" name="PHONE" placeholder="Phone">
<!-- Single file field -->
<input type="file" name="FILE">
<!-- Multiple files field -->
<input type="file" name="FILES" multiple>
<!-- Submit button -->
<input type="submit" value="Submit">
</form>
<!-- Include jQuery for the AJAX request -->
<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) {
el.preventDefault();
var formData = new FormData(this); // Collect form data including files
$.ajax({
method: 'POST',
url: '/form', // Flask handler route
data: formData,
processData: false,
contentType: false,
dataType: 'json',
success: function(data) {
alert(data.message);
},
error: function() {
alert('Error during form submission');
}
});
});
});
</script>
2. Create a Form Handler
To process values from form fields and add a lead to the CRM, we will create a handler.
Prepare Form Data
To use data from the form in the lead creation method, you must prepare it.
Strip HTML Tags
Retrieve the form data and strip HTML tags.
// Get data from the form
const sName = String(req.body.NAME ?? '')
const sLastName = String(req.body.LAST_NAME ?? '')
const sCompanyTitle = String(req.body.COMPANY_TITLE ?? '')
const sPhone = String(req.body.PHONE ?? '')
const sEmail = String(req.body.EMAIL ?? '')
// Get and sanitize data from the form
$sName = htmlspecialchars($_POST["NAME"]);
$sLastName = htmlspecialchars($_POST["LAST_NAME"]);
$sCompanyTitle = htmlspecialchars($_POST["COMPANY_TITLE"]);
$sPhone = htmlspecialchars($_POST["PHONE"]);
$sEmail = htmlspecialchars($_POST["EMAIL"]);
# Get data from the form
s_name = request.form.get("NAME", "")
s_last_name = request.form.get("LAST_NAME", "")
s_company_title = request.form.get("COMPANY_TITLE", "")
s_phone = request.form.get("PHONE", "")
s_email = request.form.get("EMAIL", "")
Prepare Files
Prepare files for upload to Bitrix24. For each file, you must pass an array containing:
- the file name,
- a string containing the file encoded in Base64.
To encode a file, use the base64_encode function.
Documentation
// Create variables for arrays with files
const arFiles = []
let arSingleFile = []
// Process the FILES field with multiple files (multer stores them in req.files)
for (const file of req.files?.FILES ?? []) {
arFiles.push({
fileData: [
file.originalname, // filename
file.buffer.toString('base64'), // file content, encoded in base64
]
})
}
// Process the FILE field with a single file
const single = req.files?.FILE?.[0]
if (single) {
arSingleFile = {
fileData: [
single.originalname, // filename
single.buffer.toString('base64'), // file content, encoded in base64
]
}
}
// Create variables for arrays with files
$arFiles = [];
$arSingleFile = [];
// Process the FILES field with multiple files
if(!empty($_FILES['FILES']['tmp_name'])) {
foreach($_FILES['FILES']['tmp_name'] as $key => $tmpName) {
if(!empty($tmpName)) {
$arFiles[] = [
'fileData' => [
$_FILES['FILES']['name'][$key], // filename
base64_encode(file_get_contents($tmpName)) // file content, encoded in base64
]
];
}
}
}
// Process the FILE field with a single file
if(!empty($_FILES['FILE']['tmp_name'])) {
$arSingleFile = [
'fileData' => [
$_FILES['FILE']['name'], // filename
base64_encode(file_get_contents($_FILES['FILE']['tmp_name'])) // file content, encoded in base64
]
];
}
import base64
# Create variables for arrays with files
ar_files = []
ar_single_file = []
# Process the FILES field with multiple files
for file in request.files.getlist("FILES"):
if file and file.filename:
ar_files.append({
"fileData": [
file.filename, # filename
base64.b64encode(file.read()).decode(), # file content, encoded in base64
]
})
# Process the FILE field with a single file
single = request.files.get("FILE")
if single and single.filename:
ar_single_file = {
"fileData": [
single.filename, # filename
base64.b64encode(single.read()).decode(), # file content, encoded in base64
]
}
Format Phone and Email
The system stores phone and email as an array of crm_multifield objects, so they must be converted to an array format.
-
If a value exists, add it as the first item
VALUEin the array, and specify the typeVALUE_TYPEas the second value, for example:WORK— for phone,HOME— for email.
-
If no value exists, pass an empty array.
// Format phone and email for Bitrix24 into crm_multifield format
const arPhone = sPhone ? [{ VALUE: sPhone, VALUE_TYPE: 'WORK' }] : []
const arEmail = sEmail ? [{ VALUE: sEmail, VALUE_TYPE: 'HOME' }] : []
// Format phone and email for Bitrix24 into crm_multifield format
$arPhone = (!empty($sPhone)) ? array(array('VALUE' => $sPhone, 'VALUE_TYPE' => 'WORK')) : array();
$arEmail = (!empty($sEmail)) ? array(array('VALUE' => $sEmail, 'VALUE_TYPE' => 'HOME')) : array();
# Format phone and email for Bitrix24 into crm_multifield format
ar_phone = [{"VALUE": s_phone, "VALUE_TYPE": "WORK"}] if s_phone else []
ar_email = [{"VALUE": s_email, "VALUE_TYPE": "HOME"}] if s_email else []
Formulate the Lead Heading
Formulate the lead heading using the first and last name. For companies, add the company name to the heading.
// Form the lead title from first and last name
let sTitle = 'From website: ' + `${sName} ${sLastName}`.trim()
// If there is a company name — add it via a hyphen after the first and last name
if (sCompanyTitle) {
sTitle += ' — ' + sCompanyTitle
}
// Form the lead title from first and last name
$sTitle = 'From website: ' . trim($sName . ' ' . $sLastName);
// If there is a company name — add it via a hyphen after the first and last name
if (!empty($sCompanyTitle)) {
$sTitle .= ' — ' . $sCompanyTitle;
}
# Form the lead title from first and last name
s_title = "From website: " + f"{s_name} {s_last_name}".strip()
# If there is a company name — add it after the first and last name using a hyphen
if s_company_title:
s_title += " — " + s_company_title
Create a Lead
To create a lead, use the crm.lead.add method. Pass the following fields in the fields object:
TITLE— lead heading,NAME— lead first name,LAST_NAME— last name,COMPANY_TITLE— company name,PHONE— phone number,EMAIL— Email,UF_CRM_LEAD_FILES— custom field for adding multiple files,UF_CRM_LEAD_FILE— custom field for a file.
Custom fields UF_CRM_* must be created in Bitrix24 before creating the lead. Add them to the portal manually or via the crm.lead.userfield.add method. In the example, replace UF_CRM_LEAD_FILES and UF_CRM_LEAD_FILE with your own field names.
Check which required fields are configured for leads in your Bitrix24. All required fields must be passed to the crm.lead.add method.
await $b24.actions.v2.call.make({
method: 'crm.lead.add',
params: {
fields: {
TITLE: sTitle, // Lead title
NAME: sName, // First name
LAST_NAME: sLastName, // Last name
COMPANY_TITLE: sCompanyTitle, // Company name
PHONE: arPhone, // Phone number
EMAIL: arEmail, // Email
UF_CRM_LEAD_FILES: arFiles, // Field for adding multiple files
UF_CRM_LEAD_FILE: arSingleFile, // File field
}
},
requestId: 'lead-add'
})
$sb->getCRMScope()->lead()->add([
'TITLE' => $sTitle, // Lead title
'NAME' => $sName, // First name
'LAST_NAME' => $sLastName, // Last name
'COMPANY_TITLE' => $sCompanyTitle, // Company name
'PHONE' => $arPhone, // Phone number
'EMAIL' => $arEmail, // Email
'UF_CRM_LEAD_FILES' => $arFiles, // Field for adding multiple files
'UF_CRM_LEAD_FILE' => $arSingleFile, // File field
]);
client.crm.lead.add(fields={
"TITLE": s_title, # Lead title
"NAME": s_name, # First name
"LAST_NAME": s_last_name, # Last name
"COMPANY_TITLE": s_company_title, # Company name
"PHONE": ar_phone, # Phone number
"EMAIL": ar_email, # Email
"UF_CRM_LEAD_FILES": ar_files, # Field for adding multiple files
"UF_CRM_LEAD_FILE": ar_single_file, # File field
})
As a result, you will receive the identifier of the new lead 5.
{
"result": 5
}
Full Handler Code Example
import express from 'express'
import multer from 'multer'
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/'
const app = express()
// multer stores files in memory — available as Buffer in req.files
const upload = multer({ storage: multer.memoryStorage() })
// The handler accepts form data (multipart) via the /form route
app.post('/form', upload.fields([{ name: 'FILE' }, { name: 'FILES' }]), async (req, res) => {
// Get and sanitize data from the form
const sName = String(req.body.NAME ?? '')
const sLastName = String(req.body.LAST_NAME ?? '')
const sCompanyTitle = String(req.body.COMPANY_TITLE ?? '')
const sPhone = String(req.body.PHONE ?? '')
const sEmail = String(req.body.EMAIL ?? '')
// Create variables for arrays with files
const arFiles = []
let arSingleFile = []
// Process the FILES field with multiple files
for (const file of req.files?.FILES ?? []) {
arFiles.push({
fileData: [
file.originalname, // filename
file.buffer.toString('base64'), // file content, encoded in base64
]
})
}
// Process the FILE field with a single file
const single = req.files?.FILE?.[0]
if (single) {
arSingleFile = {
fileData: [
single.originalname, // filename
single.buffer.toString('base64'), // file content, encoded in base64
]
}
}
// Format phone and email for Bitrix24 into crm_multifield format
const arPhone = sPhone ? [{ VALUE: sPhone, VALUE_TYPE: 'WORK' }] : []
const arEmail = sEmail ? [{ VALUE: sEmail, VALUE_TYPE: 'HOME' }] : []
// Form the lead title from first and last name
let sTitle = 'From website: ' + `${sName} ${sLastName}`.trim()
if (sCompanyTitle) {
sTitle += ' — ' + sCompanyTitle
}
// Sending data to Bitrix24
const response = await $b24.actions.v2.call.make({
method: 'crm.lead.add',
params: {
fields: {
TITLE: sTitle, // Lead title
NAME: sName, // First name
LAST_NAME: sLastName, // Last name
COMPANY_TITLE: sCompanyTitle, // Company name
PHONE: arPhone, // Phone number
EMAIL: arEmail, // Email
UF_CRM_LEAD_FILES: arFiles, // Field for adding multiple files
UF_CRM_LEAD_FILE: arSingleFile, // File field
}
},
requestId: 'lead-add'
})
// Check the result and display a message
if (response.isSuccess && response.getData()?.result) {
res.json({ message: 'Lead added successfully' })
} else {
res.json({ message: 'Lead not added: ' + response.getErrorMessages().join('; ') })
}
})
app.listen(3000)
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
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.com/rest/USER_ID/TOKEN/');
// Get and sanitize data from the form
$sName = htmlspecialchars($_POST["NAME"]);
$sLastName = htmlspecialchars($_POST["LAST_NAME"]);
$sCompanyTitle = htmlspecialchars($_POST["COMPANY_TITLE"]);
$sPhone = htmlspecialchars($_POST["PHONE"]);
$sEmail = htmlspecialchars($_POST["EMAIL"]);
// Create variables for arrays with files
$arFiles = [];
$arSingleFile = [];
// Process the FILES field with multiple files
if (!empty($_FILES['FILES']['tmp_name'])) {
foreach ($_FILES['FILES']['tmp_name'] as $key => $tmpName) {
if (!empty($tmpName)) {
$arFiles[] = [
'fileData' => [
$_FILES['FILES']['name'][$key], // filename
base64_encode(file_get_contents($tmpName)) // file content, encoded in base64
]
];
}
}
}
// Process the FILE field with a single file
if (!empty($_FILES['FILE']['tmp_name'])) {
$arSingleFile = [
'fileData' => [
$_FILES['FILE']['name'], // filename
base64_encode(file_get_contents($_FILES['FILE']['tmp_name'])) // file content, encoded in base64
]
];
}
// Format phone and email for Bitrix24 into crm_multifield format
$arPhone = (!empty($sPhone)) ? array(array('VALUE' => $sPhone, 'VALUE_TYPE' => 'WORK')) : array();
$arEmail = (!empty($sEmail)) ? array(array('VALUE' => $sEmail, 'VALUE_TYPE' => 'HOME')) : array();
// Form the lead title from first and last name
$sTitle = 'From website: ' . trim($sName . ' ' . $sLastName);
if (!empty($sCompanyTitle)) {
$sTitle .= ' — ' . $sCompanyTitle;
}
// Sending data to Bitrix24
try {
$sb->getCRMScope()->lead()->add([
'TITLE' => $sTitle, // Lead title
'NAME' => $sName, // First name
'LAST_NAME' => $sLastName, // Last name
'COMPANY_TITLE' => $sCompanyTitle, // Company name
'PHONE' => $arPhone, // Phone number
'EMAIL' => $arEmail, // Email
'UF_CRM_LEAD_FILES' => $arFiles, // Field for adding multiple files
'UF_CRM_LEAD_FILE' => $arSingleFile, // File field
]);
echo json_encode(['message' => 'Lead added successfully']);
} catch (\Throwable $e) {
echo json_encode(['message' => 'Lead not added: ' . $e->getMessage()]);
}
# pip install b24pysdk
import base64
from flask import Flask, request, jsonify
from b24pysdk import BitrixWebhook, Client
app = Flask(__name__)
client = Client(BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token="USER_ID/TOKEN", # user_id/token only, without https://
))
@app.route("/form", methods=["POST"])
def handle_form():
# Get data from the form
s_name = request.form.get("NAME", "")
s_last_name = request.form.get("LAST_NAME", "")
s_company_title = request.form.get("COMPANY_TITLE", "")
s_phone = request.form.get("PHONE", "")
s_email = request.form.get("EMAIL", "")
# Create variables for arrays with files
ar_files = []
ar_single_file = []
# Process the FILES field with multiple files
for file in request.files.getlist("FILES"):
if file and file.filename:
ar_files.append({
"fileData": [
file.filename, # filename
base64.b64encode(file.read()).decode(), # file content, encoded in base64
]
})
# Process the FILE field with a single file
single = request.files.get("FILE")
if single and single.filename:
ar_single_file = {
"fileData": [
single.filename, # filename
base64.b64encode(single.read()).decode(), # file content, encoded in base64
]
}
# Format phone and email for Bitrix24 into crm_multifield format
ar_phone = [{"VALUE": s_phone, "VALUE_TYPE": "WORK"}] if s_phone else []
ar_email = [{"VALUE": s_email, "VALUE_TYPE": "HOME"}] if s_email else []
# Form the lead title from first and last name
s_title = "From website: " + f"{s_name} {s_last_name}".strip()
if s_company_title:
s_title += " — " + s_company_title
# Sending data to Bitrix24
try:
client.crm.lead.add(fields={
"TITLE": s_title, # Lead title
"NAME": s_name, # First name
"LAST_NAME": s_last_name, # Last name
"COMPANY_TITLE": s_company_title, # Company name
"PHONE": ar_phone, # Phone number
"EMAIL": ar_email, # Email
"UF_CRM_LEAD_FILES": ar_files, # Field for adding multiple files
"UF_CRM_LEAD_FILE": ar_single_file, # File field
})
return jsonify({"message": "Lead added successfully"})
except Exception as e:
return jsonify({"message": f"Lead not added: {e}"})