How to Link a Contact and a Deal to Sales Intelligence

Scope: crm

Who can execute the methods:

  • to create a contact and a deal — a user with permission to add these CRM objects
  • to link a deal to a contact via contactIds — a user with permission to read this contact
  • to attach a trace — a user with permission to edit the created contact and deal

If you are developing integrations for Bitrix24 using AI tools (Codex, Claude Code, Cursor), connect to the MCP server so that the assistant can utilize the official REST documentation.

Sales Intelligence links a customer inquiry to the referral source and visited website pages. This data is stored in a trace — a JSON string generated by the Bitrix24 Sales Intelligence script.

In this example, a visitor submits a feedback form. As a result, a contact, a linked deal, and data regarding the customer's path prior to the inquiry are saved in the CRM.

The scenario consists of four steps.

  1. Retrieve the visitor's trace using the b24Tracker.guest.getTrace() function
  2. Create a contact using the crm.item.add method
  3. Create a deal using the same method and link it to the contact
  4. Attach the contact and deal to the trace using the crm.tracking.trace.add method

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 and sends them to the backend via a standard POST request. The backend calls Bitrix24 methods via an SDK:

Prepare the Form and Sales Intelligence

To perform the example:

  1. Create a form handler on an external server using JS, PHP, or Python
  2. Install the SDK for the chosen language: @bitrix24/b24jssdk, bitrix24/b24phpsdk, or b24pysdk. B24PhpSDK 3.x requires PHP 8.4 or 8.5
  3. Create an incoming webhook with the crm scope on behalf of a user with the required permissions
  4. Install the Sales Intelligence script generated by Bitrix24 before the </body> closing tag on all pages where visitor routes need to be collected, including the form page. After the script loads, the b24Tracker.guest.getTrace() function must be available on the page

For server-side JS examples with B24Hook, Node.js 20 or 22 and higher is required. B24JsSDK is an ES module: save the code in a file named .mjs or add "type": "module" to package.json. These requirements do not apply to the browser script with b24Tracker.

For examples using b24pysdk, Python 3.9 or newer is required.

1. Retrieve the Visitor's Trace

Add a TRACE hidden field to the form. Before submitting the form, call b24Tracker.guest.getTrace() and save the result in this field.

The function returns a JSON string and clears the accumulated trace after reading. Call it once immediately before submitting the form.

How to Use Examples in Documentation

<form id="feedback-form" method="post">
            <input type="hidden" id="form-trace" name="TRACE">
            <!-- form fields -->
            <button type="submit">Send</button>
        </form>
        
        <p id="message" aria-live="polite"></p>
        
        <script>
            const form = document.getElementById('feedback-form');
            const traceInput = document.getElementById('form-trace');
            const message = document.getElementById('message');
        
            form.addEventListener('submit', function(event) {
                const tracker = window.b24Tracker && window.b24Tracker.guest;
        
                if (!tracker || typeof tracker.getTrace !== 'function') {
                    event.preventDefault();
                    message.textContent = 'Failed to retrieve end-to-end analytics data';
                    return;
                }
        
                const trace = tracker.getTrace();
        
                if (!trace) {
                    event.preventDefault();
                    message.textContent = 'End-to-end analytics trace is empty';
                    return;
                }
        
                traceInput.value = trace;
            });
        </script>
        

If the Sales Intelligence script is not loaded or did not return a trace, the example cancels the form submission. This prevents the creation of a contact and a deal without analytics data.

2. Create a Contact

Call crm.item.add with entityTypeId = 3 — this is the identifier for the Contact object type.

Pass the following in fields:

  • name — the name from the form
  • lastName — the surname from the form
  • fm — the contact phone number

Pass the fm field as an array because the phone number in the CRM is stored as a crm_multifield type multiple field. For the phone, specify:

  • typeId — the PHONE multiple field type
  • valueType — the value type, for example WORK
  • value — the phone number

Check which mandatory fields are configured for contacts and deals in your Bitrix24. All mandatory fields must be passed in the corresponding crm.item.add call.

// npm install @bitrix24/b24jssdk
        import { B24Hook } from '@bitrix24/b24jssdk'
        
        const $b24 = B24Hook.fromWebhookUrl('https://your-domain.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/')
        
        // name, lastName, phone are received from form data
        const contactResponse = await $b24.actions.v2.call.make({
            method: 'crm.item.add',
            params: {
                entityTypeId: 3,
                fields: {
                    name: name,
                    lastName: lastName,
                    fm: [
                        { typeId: 'PHONE', valueType: 'WORK', value: phone },
                    ],
                },
            },
            requestId: 'contact-add',
        })
        
        if (!contactResponse.isSuccess) {
            throw new Error(contactResponse.getErrorMessages().join('; '))
        }
        
        const contactId = contactResponse.getData().result.item.id
        
<?php
        // composer require bitrix24/b24phpsdk:"^3.0"
        require_once 'vendor/autoload.php';
        
        use Bitrix24\SDK\Services\ServiceBuilderFactory;
        
        $webhookUrl = 'https://your-domain.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/';
        $b24 = ServiceBuilderFactory::createServiceBuilderFromWebhook($webhookUrl);
        
        // $name, $lastName, $phone are received from form data
        $contactId = $b24->getCRMScope()->item()->add(3, [
            'name' => $name,
            'lastName' => $lastName,
            'fm' => [
                ['typeId' => 'PHONE', 'valueType' => 'WORK', 'value' => $phone],
            ],
        ])->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 are received from form data
        bitrix_response = client.crm.item.add(
            fields={
                "name": name,
                "lastName": last_name,
                "fm": [
                    {"typeId": "PHONE", "valueType": "WORK", "value": phone},
                ],
            },
            entity_type_id=3,
        ).response
        contact_id = bitrix_response.result["item"]["id"]
        

Shortened response:

{
            "result": {
                "item": {
                    "id": 101
                }
            }
        }
        

Retain result.item.id. The contact identifier will be required to create a deal and link the trace.

Call crm.item.add again. Pass entityTypeId = 2 for the deal.

Pass an array of linked contact identifiers in the contactIds field. In this scenario, the array contains the contactId obtained in the previous step.

const dealResponse = await $b24.actions.v2.call.make({
            method: 'crm.item.add',
            params: {
                entityTypeId: 2,
                fields: {
                    title: `Inquiry from website: ${name} ${lastName}`,
                    contactIds: [contactId],
                },
            },
            requestId: 'deal-add',
        })
        
        if (!dealResponse.isSuccess) {
            throw new Error(dealResponse.getErrorMessages().join('; '))
        }
        
        const dealId = dealResponse.getData().result.item.id
        
$dealId = $b24->getCRMScope()->item()->add(2, [
            'title' => 'Inquiry from website: ' . $name . ' ' . $lastName,
            'contactIds' => [$contactId],
        ])->item()->id;
        
bitrix_response = client.crm.item.add(
            fields={
                "title": "Inquiry from website: %s %s" % (name, last_name),
                "contactIds": [contact_id],
            },
            entity_type_id=2,
        ).response
        deal_id = bitrix_response.result["item"]["id"]
        

The response has the same structure as when creating a contact. Retain the deal's result.item.id. There are now two identifiers for the ENTITIES parameter: contactId and dealId.

After creating the contact and the deal, call crm.tracking.trace.add, because TRACE cannot be passed directly to crm.item.add.

You can pass UTM fields in the fields of the crm.item.add method: utmSource, utmMedium, utmCampaign, utmContent, utmTerm. These retain advertising tags in the CRM object but do not replace a full trace containing the website visit route.

In crm.tracking.trace.add, pass:

  • TRACE — the JSON string from the hidden form field
  • ENTITIES — a contact of type CONTACT and a deal of type DEAL
const traceResponse = await $b24.actions.v2.call.make({
            method: 'crm.tracking.trace.add',
            params: {
                TRACE: trace,
                ENTITIES: [
                    { TYPE: 'CONTACT', ID: contactId },
                    { TYPE: 'DEAL', ID: dealId },
                ],
            },
            requestId: 'trace-add',
        })
        
        if (!traceResponse.isSuccess) {
            throw new Error(traceResponse.getErrorMessages().join('; '))
        }
        
        const traceId = traceResponse.getData().result
        
// crm.tracking.* is not among the typed services — calling via core
        $b24->core->call('crm.tracking.trace.add', [
            'TRACE' => $trace,
            'ENTITIES' => [
                ['TYPE' => 'CONTACT', 'ID' => $contactId],
                ['TYPE' => 'DEAL', 'ID' => $dealId],
            ],
        ]);
        
trace_id = client.crm.tracking.trace.add(
            trace=trace,
            entities=[
                {"TYPE": "CONTACT", "ID": contact_id},
                {"TYPE": "DEAL", "ID": deal_id},
            ],
        ).response.result
        

The method will return the numeric identifier of the created trace. The JS and Python examples retain it in a variable. In PHP, a successful call execution is sufficient: the SDK core will throw an exception if the REST API returns an error.

{
            "result": 341
        }
        

Full Example

Below is the complete scenario code for each SDK.

// 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: true }))
        
        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">
                <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 end-to-end analytics script must be installed on the page -->
            <script>
                const form = document.getElementById('feedback-form');
                const traceInput = document.getElementById('form-trace');
                const message = document.getElementById('message');
        
                form.addEventListener('submit', function(event) {
                    const tracker = window.b24Tracker && window.b24Tracker.guest;
                    if (!tracker || typeof tracker.getTrace !== 'function') {
                        event.preventDefault();
                        message.textContent = 'Failed to retrieve end-to-end analytics data';
                        return;
                    }
        
                    const trace = tracker.getTrace();
                    if (!trace) {
                        event.preventDefault();
                        message.textContent = 'End-to-end analytics trace is empty';
                        return;
                    }
        
                    traceInput.value = trace;
                });
            </script>
        </body>
        </html>`
        
        const HTML_ESCAPES = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#039;',
        }
        const escapeHtml = (value) => String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPES[char])
        const formPage = (message = '') => PAGE.replace('__MESSAGE__', escapeHtml(message))
        
        app.get('/', (req, res) => res.send(formPage()))
        
        app.post('/', async (req, res) => {
            const { NAME = '', LAST_NAME = '', PHONE = '', TRACE = '' } = req.body
        
            if (!NAME.trim() || !LAST_NAME.trim() || !PHONE.trim()) {
                return res.send(formPage('Please fill in your first name, last name, and phone number'))
            }
            if (!TRACE.trim()) {
                return res.send(formPage('Failed to retrieve end-to-end analytics trace'))
            }
            try {
                JSON.parse(TRACE)
            } catch {
                return res.send(formPage('End-to-end analytics trace contains invalid JSON'))
            }
        
            const $b24 = B24Hook.fromWebhookUrl(WEBHOOK)
            let contactId = 0
            let dealId = 0
        
            try {
                const contactResponse = await $b24.actions.v2.call.make({
                    method: 'crm.item.add',
                    params: {
                        entityTypeId: 3,
                        fields: {
                            name: NAME,
                            lastName: LAST_NAME,
                            fm: [{ typeId: 'PHONE', valueType: 'WORK', value: PHONE }],
                        },
                    },
                    requestId: 'contact-add',
                })
                if (!contactResponse.isSuccess) {
                    return res.send(formPage('Contact not created: ' + contactResponse.getErrorMessages().join('; ')))
                }
                contactId = contactResponse.getData().result.item.id
        
                const dealResponse = await $b24.actions.v2.call.make({
                    method: 'crm.item.add',
                    params: {
                        entityTypeId: 2,
                        fields: {
                            title: `Inquiry from website: ${NAME} ${LAST_NAME}`,
                            contactIds: [contactId],
                        },
                    },
                    requestId: 'deal-add',
                })
                if (!dealResponse.isSuccess) {
                    return res.send(formPage(
                        'Contact ' + contactId + ' created, but deal was not created: '
                        + dealResponse.getErrorMessages().join('; ')
                    ))
                }
                dealId = dealResponse.getData().result.item.id
        
                const traceResponse = await $b24.actions.v2.call.make({
                    method: 'crm.tracking.trace.add',
                    params: {
                        TRACE,
                        ENTITIES: [
                            { TYPE: 'CONTACT', ID: contactId },
                            { TYPE: 'DEAL', ID: dealId },
                        ],
                    },
                    requestId: 'trace-add',
                })
                if (!traceResponse.isSuccess) {
                    return res.send(formPage(
                        'Contact ' + contactId + ' and deal ' + dealId
                        + ' created, but trace is not linked: '
                        + traceResponse.getErrorMessages().join('; ')
                    ))
                }
        
                const traceId = traceResponse.getData().result
                return res.send(formPage(
                    'Contact ' + contactId + ', deal ' + dealId
                    + ' and trace ' + traceId + ' created'
                ))
            } catch (error) {
                if (dealId > 0) {
                    return res.send(formPage(
                        'Contact ' + contactId + ' and deal ' + dealId
                        + ' created, but trace is not linked: ' + error.message
                    ))
                }
                if (contactId > 0) {
                    return res.send(formPage(
                        'Contact ' + contactId + ' created, but deal was not created: ' + error.message
                    ))
                }
                return res.send(formPage('Contact not created: ' + error.message))
            } finally {
                $b24.destroy()
            }
        })
        
        app.listen(3000, () => console.log('http://localhost:3000'))
        
<?php
        // composer require bitrix24/b24phpsdk:"^3.0"
        require_once 'vendor/autoload.php';
        
        use Bitrix24\SDK\Services\ServiceBuilderFactory;
        
        $message = '';
        
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $name = trim((string)($_POST['NAME'] ?? ''));
            $lastName = trim((string)($_POST['LAST_NAME'] ?? ''));
            $phone = trim((string)($_POST['PHONE'] ?? ''));
            $trace = trim((string)($_POST['TRACE'] ?? ''));
        
            if ($name === '' || $lastName === '' || $phone === '') {
                $message = 'Please fill in your first name, last name, and phone number';
            } elseif ($trace === '') {
                $message = 'Failed to retrieve end-to-end analytics trace';
            } else {
                json_decode($trace, true);
                if (json_last_error() !== JSON_ERROR_NONE) {
                    $message = 'End-to-end analytics trace contains invalid JSON';
                } else {
                    $webhookUrl = 'https://your-domain.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/';
                    $b24 = ServiceBuilderFactory::createServiceBuilderFromWebhook($webhookUrl);
        
                    $contactId = 0;
                    $dealId = 0;
        
                    try {
                        $contactId = $b24->getCRMScope()->item()->add(3, [
                            'name' => $name,
                            'lastName' => $lastName,
                            'fm' => [
                                ['typeId' => 'PHONE', 'valueType' => 'WORK', 'value' => $phone],
                            ],
                        ])->item()->id;
        
                        $dealId = $b24->getCRMScope()->item()->add(2, [
                            'title' => 'Inquiry from website: ' . $name . ' ' . $lastName,
                            'contactIds' => [$contactId],
                        ])->item()->id;
        
                        // crm.tracking.* is not among the typed services — calling via core
                        $b24->core->call('crm.tracking.trace.add', [
                            'TRACE' => $trace,
                            'ENTITIES' => [
                                ['TYPE' => 'CONTACT', 'ID' => $contactId],
                                ['TYPE' => 'DEAL', 'ID' => $dealId],
                            ],
                        ]);
        
                        $message = 'Contact ' . $contactId . ' and deal ' . $dealId
                            . ' created and linked to end-to-end analytics';
                    } catch (\Throwable $error) {
                        if ($dealId > 0) {
                            $message = 'Contact ' . $contactId . ' and deal ' . $dealId
                                . ' created, but trace is not linked: ' . $error->getMessage();
                        } elseif ($contactId > 0) {
                            $message = 'Contact ' . $contactId
                                . ' created, but deal was not created: ' . $error->getMessage();
                        } else {
                            $message = 'Contact not created: ' . $error->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">
                <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 end-to-end analytics script must be installed on the page -->
            <script>
                const form = document.getElementById('feedback-form');
                const traceInput = document.getElementById('form-trace');
                const message = document.getElementById('message');
        
                form.addEventListener('submit', function(event) {
                    const tracker = window.b24Tracker && window.b24Tracker.guest;
                    if (!tracker || typeof tracker.getTrace !== 'function') {
                        event.preventDefault();
                        message.textContent = 'Failed to retrieve end-to-end analytics data';
                        return;
                    }
        
                    const trace = tracker.getTrace();
                    if (!trace) {
                        event.preventDefault();
                        message.textContent = 'End-to-end analytics trace is empty';
                        return;
                    }
        
                    traceInput.value = trace;
                });
            </script>
        </body>
        </html>
        
# pip install b24pysdk flask
        import html
        import json
        
        from flask import Flask, request
        from b24pysdk import Client, BitrixWebhook
        
        WEBHOOK_DOMAIN = "your-domain.bitrix24.com"
        WEBHOOK_TOKEN = "1/xxxxxxxxxxxxxxxx"
        
        app = Flask(__name__)
        client = Client(BitrixWebhook(
            domain=WEBHOOK_DOMAIN,
            webhook_token=WEBHOOK_TOKEN,
        ))
        
        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)s</p>
            <form id="feedback-form" method="post">
                <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 end-to-end analytics script must be installed on the page -->
            <script>
                const form = document.getElementById('feedback-form');
                const traceInput = document.getElementById('form-trace');
                const message = document.getElementById('message');
        
                form.addEventListener('submit', function(event) {
                    const tracker = window.b24Tracker && window.b24Tracker.guest;
                    if (!tracker || typeof tracker.getTrace !== 'function') {
                        event.preventDefault();
                        message.textContent = 'Failed to retrieve end-to-end analytics data';
                        return;
                    }
        
                    const trace = tracker.getTrace();
                    if (!trace) {
                        event.preventDefault();
                        message.textContent = 'End-to-end analytics trace is empty';
                        return;
                    }
        
                    traceInput.value = trace;
                });
            </script>
        </body>
        </html>"""
        
        def form_page(message: str = "") -> str:
            return PAGE % {"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")
            if not trace:
                return form_page("Failed to retrieve end-to-end analytics trace")
            try:
                json.loads(trace)
            except json.JSONDecodeError:
                return form_page("End-to-end analytics trace contains invalid JSON")
        
            contact_id = 0
            deal_id = 0
        
            try:
                bitrix_response = client.crm.item.add(
                    fields={
                        "name": name,
                        "lastName": last_name,
                        "fm": [
                            {"typeId": "PHONE", "valueType": "WORK", "value": phone},
                        ],
                    },
                    entity_type_id=3,
                ).response
                contact_id = bitrix_response.result["item"]["id"]
        
                bitrix_response = client.crm.item.add(
                    fields={
                        "title": "Inquiry from website: %s %s" % (name, last_name),
                        "contactIds": [contact_id],
                    },
                    entity_type_id=2,
                ).response
                deal_id = bitrix_response.result["item"]["id"]
        
                trace_id = client.crm.tracking.trace.add(
                    trace=trace,
                    entities=[
                        {"TYPE": "CONTACT", "ID": contact_id},
                        {"TYPE": "DEAL", "ID": deal_id},
                    ],
                ).response.result
        
                return form_page(
                    "Contact %s, deal %s, and trace %s have been created"
                    % (contact_id, deal_id, trace_id)
                )
            except Exception as error:
                if deal_id:
                    return form_page(
                        "Contact %s and deal %s were created, but trace is not linked: %s"
                        % (contact_id, deal_id, error)
                    )
                if contact_id:
                    return form_page(
                        "Contact %s was created, but deal was not created: %s" % (contact_id, error)
                    )
                return form_page("Contact not created: %s" % error)
        
        if __name__ == "__main__":
            app.run(host="0.0.0.0", port=3000)
        

Verify the Result

  1. Open the created contact in the CRM and check the first name, last name, and phone number
  2. Open the created deal and verify that this contact is linked to it
  3. Ensure that the page message confirms the linking of objects to Sales Intelligence. For JS and Python, the message also contains a numeric trace identifier

If the message indicates that the deal was not created or the trace was not linked, the scenario completed partially. Check the already created objects before rerunning the scenario.

Error Handling

Contact and Deal Creation Errors

The crm.item.add method returns an error code in the error field.

error Reason What to Check
ACCESS_DENIED No permission to add a contact or a deal Permissions of the user on whose behalf the webhook was created
CRM_FIELD_ERROR_REQUIRED A required field is not filled Required fields of the contact or the deal
CRM_FIELD_ERROR_VALUE_NOT_VALID Field value or type failed validation Values and types of contact or deal fields; for fm, check the structure of the array elements
100 Invalid value type for the multiple field fm Ensure that fm is passed as an array of elements

Trace Linking Errors

For the following parameter checks of TRACE, ENTITIES, and permissions, the crm.tracking.trace.add method returns code ERROR_CORE in the error field. System REST errors may have different codes. The specific error reason is provided in the field error_description.

error_description Reason What to Check
Parameter `TRACE` required. Trace not passed Loading of the Sales Intelligence script and the hidden field TRACE
Can not parse JSON in parameter `TRACE`. TRACE is not a valid JSON string The result of b24Tracker.guest.getTrace()
Wrong TYPE in parameter `ENTITIES`. Allowed types: COMPANY,CONTACT,DEAL,LEAD,QUOTE An invalid object type was passed Values of TYPE in the ENTITIES array
Wrong ID in parameter `ENTITIES`. An empty, non-numeric, or non-positive object identifier was passed Values of ID in the ENTITIES array
You have no access to entity `CONTACT` with ID `123`. No permission to modify the object from ENTITIES User permissions for the specified object

In the last message, CONTACT and 123 are provided as examples. The method substitutes the actual object type and identifier.

Calls are executed sequentially and are not combined into a transaction. If the deal creation or trace linking fails with an error, previously created objects will remain in the CRM.

Key Considerations

  • resubmitting the form will create a new contact and a new deal
  • protect the public form from automated submissions, for example, using CAPTCHA and rate limiting

Continue Learning