Форма зворотного зв’язку, що шле листи людині у відпустці, — ідеальне місце, де заявки тихо помирають. З Diil кожна форма на вашому сайті потрапляє в CRM карткою — з телефоном чи поштою, статусом і відповідальним, який має передзвонити. Вистачить одного атрибута на <form>, а коли потрібно більше контролю, є варіанти з JavaScript і з сервера.
Три способи надіслати заявку — обирайте, який зручніший:
- Форма з
data-crm-lead— нуль JavaScript. Віджет перехоплює надсилання й робить усе сам. window.crmLead()— для React, Vue і будь-якої форми, якою ви керуєте самі.POST /marketing/leadз вашого сервера — замовлення телефоном, форми на бекенді, інтеграції з іншими системами.
Як заявка потрапляє з форми в CRM
У Diil заявка — це особливий різновид події. Кожна подія має ім’я — скажімо, contact_form, — і це ім’я спершу треба оголосити в CRM. Позначте в ній «Це заявка», і кожне надсилання з таким ім’ям ставатиме карткою в CRM → Заявки.
- Відвідувач заповнює форму й натискає «Надіслати».
- Віджет збирає поля, з’ясовує, хто ця людина (ім’я, телефон, пошта, повідомлення), і відправляє дані.
- Сервер перевіряє, що ім’я оголошено, що в заявці є телефон або пошта і що вона не схожа на бота.
- У CRM з’являється картка заявки — а якщо налаштуєте, то й сповіщення в Telegram.
Два правила, які варто запам’ятати одразу:
- Неоголошене ім’я відхиляється. CRM приймає лише те, про що знає, — жодного несподіваного сміття через одруківки.
- Оголошено, але не позначено як заявку? Тоді надсилання зберігається як звичайна подія, і картка заявки не створюється.
Налаштуйте тип заявки в CRM
Дві хвилини кліків, один раз на кожен тип форми:
Відкрийте Маркетинг → Події
Тут описано всі події, які може надсилати ваш сайт.Створіть подію
Дайте їй ім’я, яке використовуватиме ваш код: латинські літери, цифри й підкреслення, починаючи з літери, —contact_form,callback_request,quote_request.Позначте «Це заявка»
Тепер надсилання стають картками заявок. Як і підказує CRM, форма має передати спосіб зв’язку — телефон або пошту.За бажання: позначте «Подія приносить гроші»
Лише тоді зберігаються сума й валюта заявки. Без цієї позначки їх мовчки ігнорують.
Варіант A: форма з одним атрибутом
Додайте data-crm-lead="your_event_name" до будь-якої форми на сторінці, де є скрипт віджета. Це й уся інтеграція. Форми, що з’являються пізніше — у модальному вікні, після зміни маршруту в односторінковому застосунку, — підхоплюються автоматично.
Повний приклад
Форма «запит комерційної пропозиції» з ім’ям, телефоном, поштою й повідомленням, сумою для звіту про доходи, повідомленням «дякуємо» та власною перевіркою. Спершу чистий HTML; версії для React і Vue викликають crmLead() зі свого обробника надсилання.
<form id="quote" data-crm-lead="quote_request" data-crm-value="149900" data-crm-currency="EUR">
<label>Ваше ім’я <input name="name" autocomplete="name" required></label>
<label>Телефон <input name="phone" type="tel" autocomplete="tel"></label>
<label>Email <input name="email" type="email" autocomplete="email"></label>
<label>Що вам потрібно? <textarea name="message" rows="4"></textarea></label>
<!-- Не з відомих полів: з’явиться в картці заявки як є -->
<label>Розмір команди
<select name="team_size">
<option>1–5</option>
<option>6–20</option>
<option>20+</option>
</select>
</label>
<button type="submit">Надіслати запит</button>
<p class="form-status" role="status" hidden></p>
</form>
<script>
const form = document.getElementById('quote');
const status = form.querySelector('.form-status');
const button = form.querySelector('button');
function say(text) {
status.textContent = text;
status.hidden = false;
}
// 1. Перед надсиланням: наша власна перевірка. preventDefault() = нічого не надсилається
form.addEventListener('crm:lead-before', (event) => {
const phone = form.elements.phone.value.trim();
const email = form.elements.email.value.trim();
if (!phone && !email) {
event.preventDefault();
say('Залиште, будь ласка, телефон або email, щоб ми могли відповісти.');
return;
}
button.disabled = true;
});
// 2. Після надсилання: віджет нічого не показує, «дякуємо» — наше
form.addEventListener('crm:lead', (event) => {
button.disabled = false;
say(event.detail.ok
? 'Дякуємо! Ми відповімо протягом одного робочого дня.'
: 'Щось пішло не так. Спробуйте ще раз або зателефонуйте нам.');
});
</script>
<script src="https://widget.sitecog.com/widget.js" defer></script>'use client';
import { useState, type FormEvent } from 'react';
const EMPTY = { name: '', phone: '', email: '', message: '' };
export function QuoteForm() {
const [form, setForm] = useState(EMPTY);
const [status, setStatus] = useState<'idle' | 'sending' | 'done' | 'error'>('idle');
const set = (key: keyof typeof EMPTY) => (e: { target: { value: string } }) =>
setForm((prev) => ({ ...prev, [key]: e.target.value }));
async function onSubmit(e: FormEvent) {
e.preventDefault();
if (!form.phone.trim() && !form.email.trim()) {
setStatus('error');
return;
}
setStatus('sending');
// crmLead ніколи не кидає винятків; undefined він лише тоді, коли widget.js немає на сторінці
const res = await window.crmLead?.('quote_request', form, { value: 149900, currency: 'EUR' });
if (res?.ok) {
setForm(EMPTY);
setStatus('done');
} else {
setStatus('error');
}
}
if (status === 'done') return <p>Дякуємо! Ми відповімо протягом одного робочого дня.</p>;
// Тут немає data-crm-lead: ця форма надсилає через crmLead сама
return (
<form onSubmit={onSubmit}>
<input value={form.name} onChange={set('name')} placeholder="Ваше ім’я" required />
<input value={form.phone} onChange={set('phone')} type="tel" placeholder="Телефон" />
<input value={form.email} onChange={set('email')} type="email" placeholder="Email" />
<textarea value={form.message} onChange={set('message')} placeholder="Що вам потрібно?" />
<button disabled={status === 'sending'}>Надіслати запит</button>
{status === 'error' && <p role="alert">Залиште телефон або email і спробуйте ще раз.</p>}
</form>
);
}<script setup lang="ts">
import { reactive, ref } from 'vue';
const empty = () => ({ name: '', phone: '', email: '', message: '' });
const form = reactive(empty());
const status = ref<'idle' | 'sending' | 'done' | 'error'>('idle');
async function submit() {
if (!form.phone.trim() && !form.email.trim()) {
status.value = 'error';
return;
}
status.value = 'sending';
// crmLead ніколи не кидає винятків; undefined він лише тоді, коли widget.js немає на сторінці
const res = await window.crmLead?.('quote_request', { ...form }, { value: 149900, currency: 'EUR' });
if (res?.ok) {
Object.assign(form, empty());
status.value = 'done';
} else {
status.value = 'error';
}
}
</script>
<template>
<p v-if="status === 'done'">Дякуємо! Ми відповімо протягом одного робочого дня.</p>
<!-- Тут немає data-crm-lead: ця форма надсилає через crmLead сама -->
<form v-else @submit.prevent="submit">
<input v-model="form.name" placeholder="Ваше ім’я" required />
<input v-model="form.phone" type="tel" placeholder="Телефон" />
<input v-model="form.email" type="email" placeholder="Email" />
<textarea v-model="form.message" placeholder="Що вам потрібно?" />
<button :disabled="status === 'sending'">Надіслати запит</button>
<p v-if="status === 'error'" role="alert">Залиште телефон або email і спробуйте ще раз.</p>
</form>
</template>Що відбувається, коли форму надсилають
- Віджет завжди скасовує нативне надсилання — сторінка не перезавантажується, а
actionформи не використовується. - Він запускає на формі
crm:lead-before. Якщо хоч один обробник викличеevent.preventDefault(), історія на цьому закінчується й нічого не надсилається. - Він збирає поля через
FormData. Враховуються лише текстові значення: поля з файлами ігноруються. Кілька полів з однаковим ім’ям (група чекбоксів) склеюються через", ". Паролі, поля з чутливими іменами й усе, що позначеноdata-crm-ignore, пропускаються — див. «Поля, які ніколи не надсилаються». - Він надсилає заявку, захищену одноразовим пропуском форми (детальніше — у розділі про захист від спаму).
- Він запускає
crm:leadз{ ok: true }або{ ok: false }вevent.detail. - Якщо
okдорівнює true, він викликаєform.reset(). У разі невдачі форма лишається як є, тож відвідувач не втратить написане.
Віджет не показує жодного власного інтерфейсу — ні спливного повідомлення, ні спінера, ні «дякуємо». Ваш сайт, ваш дизайн, ваші слова: слухайте crm:lead і показуйте що завгодно.
Атрибути форми
| Атрибут | Приклад | Що робить |
|---|---|---|
data-crm-lead | "quote_request" | Перетворює форму на форму заявки. Значення — ім’я події, оголошене в CRM. Порожній атрибут означає ім’я lead_submit — його теж треба оголосити. |
data-crm-value | "149900" | Сума в мінімальних одиницях, ціле число: 149900 — це 1499,00. Зберігається, лише якщо в події позначено «Подія приносить гроші». |
data-crm-currency | "EUR" | Валюта суми: EUR, USD, UAH… |
data-crm-ignore | <fieldset data-crm-ignore> | Ставиться не на форму, а на поле або на будь-яку обгортку (fieldset, div…). Поле — чи все, що всередині обгортки, — до заявки не потрапить. Див. «Поля, які ніколи не надсилаються». |
Події форми
Обидві події спливають, тож слухати можна на самій формі або один раз на document — для всіх форм на сторінці.
| Подія | Коли | event.detail | Можна скасувати |
|---|---|---|---|
crm:lead-before | Одразу після submit, до будь-якого надсилання | { name } — ім’я події | Так: preventDefault() зупиняє надсилання |
crm:lead | Після відповіді сервера | { ok } — true, якщо заявку прийнято | Ні |
document.addEventListener('crm:lead', (event) => {
if (event.detail.ok) {
event.target.closest('.modal')?.classList.add('is-thanks');
}
});Імена полів, які розуміє CRM
Картка заявки має чотири основні слоти: ім’я, телефон, пошта й повідомлення. Віджет заповнює їх, дивлячись на імена полів. Імена обрізаються від пробілів і переводяться в нижній регістр, а потім порівнюються з цим списком точно (тож Phone спрацює, а your-phone — ні):
| Слот | Імена полів, що його заповнюють | Макс. довжина |
|---|---|---|
| Ім’я | name, fio, username, user_name, fullname, full_name, firstname, first_name, contact_name, client_name, имя, фио, ім'я | 200 |
| Телефон | phone, tel, telephone, mobile, phone_number, contact_phone, телефон, тел | 40 |
| Пошта | email, e_mail, e-mail, mail, contact_email, почта, пошта, емейл | 320 |
| Повідомлення | message, comment, comments, text, question, note, description, task, сообщение, комментарий, вопрос, повідомлення, коментар | 4000 |
- Перемагає перший збіг. Якщо у формі є і
phone, іmobile, слот заповнить те, що стоїть у формі першим. - Усе інше теж зберігається. Невідомі поля й повторні збіги потрапляють в «Інші поля форми» й показуються в картці заявки в порядку форми. До 30 додаткових полів; ключі до 60 символів, значення до 1000.
- Телефон рахується, лише якщо в ньому від 7 до 20 цифр. Пробіли, дужки й дефіси — без проблем;
+на початку зберігається. - Телефон або пошта обов’язкові. Заявку без жодного з них буде відхилено з
no_contact— відповісти ж не було б як.
Поля, які ніколи не надсилаються
Заявка — не місце для паролів і номерів карток, навіть якщо вони випадково опинилися в тій самій формі. Тож віджет пропускає:
- поля
type="password"— завжди, хоч би як вони називалися; - усе з
data-crm-ignore— на самому полі або на будь-якій обгортці (fieldset,div…): тоді пропускається все, що всередині; - поля з чутливими іменами. Ім’я розбивається на слова — за
_,-,., camelCase тощо. Як окреме слово не можна:card,cc,csc,cvv,cvc,iban,ssn,secret,pass. А ці не можна будь-де в імені:password,passwd,pwd,token,csrf,xsrf,creditcard,cardnumber,ccnum.
Тобто card_number, cvv, csrf_token і user_password до CRM не дійдуть, а discard_reason — дійде: card там не окреме слово, а шматок discard.
Якщо одне поле з якимось ім’ям виключено, виключаються всі поля з цим ім’ям, де б вони не стояли. А от приховані поля (type="hidden") надсилаються — ними зручно передати товар, тариф чи сторінку, — якщо тільки їхнє ім’я не чутливе.
<form data-crm-lead="signup_request">
<input name="name" autocomplete="name"> <!-- ✓ -->
<input name="email" type="email"> <!-- ✓ -->
<input name="account_pin" type="password"> <!-- ✗ пароль, ім’я не має значення -->
<input name="card_number" inputmode="numeric"> <!-- ✗ card — окреме слово -->
<input type="hidden" name="plan" value="pro"> <!-- ✓ приховане, піде в «Інші поля форми» -->
<fieldset data-crm-ignore> <!-- ✗ усе всередині -->
<legend>Лише для оплати</legend>
<input name="billing_address">
<input name="billing_zip">
</fieldset>
<button>Зареєструватися</button>
</form>Сервер застосовує той самий фільтр імен ще раз — до заявок з віджета, з crmLead() і навіть до POST /lead із секретним ключем. Тож поле з чутливим ім’ям в CRM не потрапить ніколи, хоч би звідки прийшла заявка.
Варіант B: надсилайте заявки з JavaScript
Коли станом форми керуєте ви — React, Vue, багатокроковий майстер, чат-бот, — викликайте віджет напряму. Обидві функції з’являються у window, щойно завантажиться widget.js.
crmLead(name, fields, options)
const { ok } = await window.crmLead(
'callback_request',
{ name: 'Anna', phone: '+49 30 1234567', message: 'Передзвоніть після 17:00, будь ласка' },
{ value: 149900, currency: 'EUR' },
);namestringобовʼязково^[A-Za-z][A-Za-z0-9_]*$). Ім’я, що не відповідає формату, дає попередження в консолі й { ok: false }.fieldsobjectобовʼязковоcard_number, csrf_token…) відкидаються так само, як у формі.options.valueintegerнеобовʼязково149900 = 1499,00. Зберігається лише для подій з позначкою «Подія приносить гроші».options.currencystringнеобовʼязковоEUR.Повертає Promise, що розв’язується в:
okbooleantrue — заявку прийнято. false — її відхилено або вона не дійшла; причину навмисно не повідомляють (див. чому).crmLeadForm(form, name)
Робить рівно те саме, що й атрибут data-crm-lead, але з коду. Знадобиться, коли форму рендерить стороння бібліотека й додати атрибути не вийде, або коли ім’я визначається під час виконання. Нічого не повертає; результат приходить через ту саму подію crm:lead.
const form = document.querySelector('#newsletter-popup form');
window.crmLeadForm(form, 'newsletter_signup');
form.addEventListener('crm:lead', (e) => {
if (e.detail.ok) form.innerHTML = '<p>Ви з нами! Перевірте пошту.</p>';
});formHTMLFormElementобовʼязковоnamestringобовʼязковоcrmLead.Оголошення для TypeScript
Віджет — звичайний скрипт, тож TypeScript про нього нічого не знає. Додайте це в будь-який файл .d.ts:
export {};
declare global {
interface Window {
crmLead?: (
name: string,
fields: Record<string, string>,
options?: { value?: number; currency?: string },
) => Promise<{ ok: boolean }>;
crmLeadForm?: (form: HTMLFormElement, name: string) => void;
}
}Як працює захист від спаму
Публічна форма — магніт для ботів. Щоб їх не пускати, CAPTCHA не потрібна: віджет і сервер справляються з цим разом, а справжні відвідувачі нічого не помічають.
Одноразовий пропуск форми
Перед надсиланням віджет отримує від сервера підписаний «пропуск форми». Щоб заощадити час, він просить його, щойно відвідувач уперше поставить курсор у форму. Пропуск:
- одноразовий — один пропуск, одна заявка;
- прив’язаний до вашого сайту — пропуск з одного сайту марний на іншому;
- дійсний 24 години.
Боти, що наосліп шлють POST на адресу, пропуску не мають і отримують відмову. А подвійний клік чи повторне надсилання з тим самим пропуском повертає { ok: true }, але заявка зберігається один раз — жодних дублікатів карток від нетерплячих пальців.
Пастка для ботів
Те саме приховане поле company_site, про яке йшлося вище. Людина його не бачить, тож воно лишається порожнім. Бот, що заповнює кожне знайдене поле, видає себе, і заявку відхиляють.
«Підозрілі» заявки
Деякі заявки виглядають дивно, але можуть бути справжніми. Їх приймають і позначають у CRM як Підозрілі, щоб менеджер міг глянути, перш ніж телефонувати:
- форму заповнили менш ніж за 3 секунди — швидше, ніж під силу людині;
- форма була відкрита понад 30 хвилин до надсилання;
- з однієї IP-адреси протягом години надійшло понад 5 заявок.
«Спам» — це статус, який людина ставить у CRM вручну. Автоматично спамом нічого не позначається, тож жодного справжнього клієнта не буде тихо викинуто.
До речі, IP-адреса в картці заявки показується обрізаною: в IPv4 останній октет обнулено (203.0.113.57 → 203.0.113.0), IPv6 урізано до /48. Правило те саме, що й для переглядів і подій.
Чому браузер отримує лише ok: true або false
Сказати боту «відхилено: заповнено пастку» — це безкоштовний урок, як прорватися. Тому з боку браузера будь-яка відмова виглядає однаково — { ok: false }, без пояснень. Налагоджуєте власну форму? Чекліст несправностей охоплює всі випадки, а серверний маршрут таки каже, що пішло не так, бо його захищено секретним ключем.
Під капотом: запит із браузера
Для допитливих — будувати це самостійно вам ніколи не доведеться. Віджет отримує пропуск з POST https://back.sitecog.com/marketing/fk (відповідь: {"pass":"…"}), а потім надсилає заявку:
POST https://back.sitecog.com/marketing/f
Content-Type: application/json
{
"n": "contact_form",
"pass": "…",
"fields": {
"name": "Anna",
"email": "anna@example.com",
"message": "Hi!",
"company_site": ""
},
"val": 149900,
"cur": "EUR",
"u": "https://shop.example/contacts",
"vid": "…",
"sid": "…"
}Відповідь — { "ok": true } або { "ok": false }. vid і sid — ідентифікатори відвідувача й сесії, тож заявка прив’язується до рекламного каналу, з якого прийшов відвідувач, — див. «Події та аналітика».
Надсилайте заявки з вашого сервера
Не кожна заявка народжується в браузері. Серверний маршрут знадобиться, коли:
- менеджер приймає замовлення телефоном і вносить його у вашу адмінку;
- форма обробляється на бекенді (PHP-обробник, server action у Next.js), і ви не хочете залежати від віджета;
- заявки приходять з іншої системи: маркетплейсу, сервісу бронювання, бота.
POST https://back.sitecog.com/marketing/lead
x-event-key: sk_…
Content-Type: application/jsonСекретні ключі
Серверний маршрут автентифікується секретним ключем. Створіть його в CRM → Маркетинг → Події → Секретні ключі → Випустити ключ. Ключ має вигляд sk_ + 48 шістнадцяткових символів; у сайту може бути до 5 активних ключів, і будь-який можна відкликати. Дайте кожному ключу назву, яка каже, де він живе («сервер оплати», «замовлення телефоном»), — ви з майбутнього будете вдячні, коли настане час котрийсь відкликати.
Приклад запиту
curl https://back.sitecog.com/marketing/lead \
-H "x-event-key: sk_3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d" \
-H "Content-Type: application/json" \
-d '{
"name": "phone_order",
"event_id": "order-1024",
"fields": {
"name": "Anna Schmidt",
"phone": "+49 30 1234567",
"message": "Two pairs of Air 3, graphite. Delivery to the office."
},
"value": 149900,
"currency": "EUR",
"url": "https://shop.example/contacts"
}'// Node 18+ — fetch уже вбудований
const res = await fetch('https://back.sitecog.com/marketing/lead', {
method: 'POST',
headers: {
'x-event-key': process.env.CRM_EVENT_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'phone_order',
event_id: `order-${order.id}`, // повтор не створить другу заявку
visitor_id: order.crmVid, // збережено з браузера під час оформлення, якщо є
session_id: order.crmSid,
fields: {
name: order.customerName,
phone: order.phone,
message: order.comment,
delivery: order.deliveryMethod, // піде в «Інші поля форми»
},
value: order.totalCents, // 149900 = 1499,00
currency: 'EUR',
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`CRM lead: ${res.status} ${data.message}`);
// data → { ok: true, duplicate: false }import os
import requests
res = requests.post(
"https://back.sitecog.com/marketing/lead",
headers={"x-event-key": os.environ["CRM_EVENT_KEY"]},
json={
"name": "phone_order",
"event_id": "order-1024",
"fields": {
"name": "Anna Schmidt",
"phone": "+49 30 1234567",
"message": "Two pairs of Air 3, graphite.",
},
"value": 149900,
"currency": "EUR",
},
timeout=10,
)
res.raise_for_status()
print(res.json()) # {'ok': True, 'duplicate': False}Тіло запиту
x-event-keyheaderstringобовʼязковоsk_… з CRM. Немає або відкликаний → 401 invalid_key.namestringобовʼязковоn.fieldsobjectобовʼязковоevent_idstringнеобовʼязковоid.visitor_idstringнеобовʼязковоsession_idstringнеобовʼязковоcrm_sid). З ним заявка успадковує рекламний канал і UTM-мітки цієї сесії. Синонім: sid.valueintegerнеобовʼязково149900 = 1499,00. Зберігається лише для подій з позначкою «Подія приносить гроші». Синонім: val.currencystringнеобовʼязковоEUR. Синонім: cur.urlstringнеобовʼязковоu.Відповідь
{ "ok": true, "duplicate": false }okbooleanduplicatebooleantrue, якщо заявка з таким event_id уже існує. Нічого нового не збережено, і це нормально.На відміну від браузера, серверний маршрут не використовує пропусків форми й перевірок часу — секретного ключа достатньо як доказу. Пастка для ботів діє й тут: непорожнє company_site у fields отримує 400 rejected.
Ідемпотентність: повторюйте без страху
Мережа падає в найгірші моменти. Запит пройшов чи ні? З event_id знати це не обов’язково: просто надішліть ще раз. Повтор поверне {"ok":true,"duplicate":true}, і заявку не буде збережено двічі.
{ "ok": true, "duplicate": true }Атрибуція: яка реклама привела заявку
Заявки з віджета прив’язуються до відвідувача автоматично. Серверна заявка нічого не знає про браузер — якщо ви їй не розкажете. Віджет зберігає ідентифікатор відвідувача в localStorage під ключем crm_vid, а ідентифікатор сесії — під crm_sid. Передайте їх на свій бекенд разом із формою чи замовленням і перешліть далі:
const crm = {
vid: localStorage.getItem('crm_vid'),
sid: localStorage.getItem('crm_sid'),
};
await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...order, crm }),
});
// …а на сервері: visitor_id: body.crm.vid, session_id: body.crm.sidЗ session_id заявка успадковує рекламний канал і UTM-мітки першого перегляду сторінки в цій сесії, тож у звітах вона з’являється поруч із кампанією, яка її заробила. Детальніше — у «Події та аналітика».
Помилки
З браузера crmLead() і подія crm:lead кажуть лише ok: false. Серверний маршрут відповідає статусом і message:
| Статус | Тіло | Що сталося |
|---|---|---|
| 400 | {"message":"unknown_event","name":"phone_order"} | У CRM немає події з таким ім’ям. Оголосіть її в Маркетинг → Події (перевірте написання й регістр). |
| 400 | {"message":"invalid_event_name"} | В імені є символи, крім латинських літер, цифр і підкреслень, або воно не починається з літери. |
| 400 | {"message":"no_contact"} | У fields немає ні телефону (7–20 цифр), ні пошти. |
| 400 | {"message":"invalid_body"} | Тіло не є JSON-об’єктом очікуваної форми. |
| 400 | {"message":"rejected"} | Заповнено поле-пастку company_site. |
| 401 | {"message":"invalid_key"} | x-event-key відсутній, має неправильний формат або відкликаний. |
| 413 | — | Тіло більше за 8 КБ. |
| 429 | {"message":"rate_limit_exceeded"} | Забагато запитів за цю хвилину. Зачекайте на наступну. |
Запити з браузера також можуть отримати 400 unknown_origin або 400 unknown_domain, коли домен сторінки не є сайтом у CRM, — ви побачите їх на вкладці Network, а код при цьому отримає ok: false.
Ліміти
| Що | Ліміт |
|---|---|
| Запити з одного IP (усі маркетингові маршрути разом) | 120 за хвилину |
| Запити на сайт | 6000 за хвилину |
| Пропуски форми з одного IP | 20 за хвилину |
| Тіло запиту | 8 КБ |
| Ім’я / телефон / пошта / повідомлення | 200 / 40 / 320 / 4000 символів |
| Додаткові поля | до 30; ключ до 60, значення до 1000 символів |
| Телефон | 7–20 цифр, щоб рахуватися контактом |
event_id | до 128 символів |
| Секретні ключі | до 5 активних на сайт |
Вікна — фіксовані хвилини: після 429 зачекайте, доки почнеться наступна хвилина. Справжній відвідувач навіть не наблизиться до лімітів; скрипт, що гатить по вашій формі, — наблизиться.
Якщо щось не так
Заявка не прийшла
Пройдіться списком — майже завжди причина в одному з цих пунктів:
- Ім’я не оголошено. У Маркетинг → Події має бути подія саме з таким ім’ям, з урахуванням регістру. Порожній
data-crm-leadозначаєlead_submit— оголосіть саме його. - Не позначено «Це заявка». Тоді надсилання зберігається як звичайна подія: шукайте його в статистиці подій, а не в Заявках.
- Немає ні телефону, ні пошти. Або вони є, але під іменами, яких CRM не знає (
your-phone,contact[email]), і потрапили в «Інші поля форми». Перейменуйте їх — див. таблицю полів. Телефон, у якому менше 7 цифр, теж не рахується. - Дані були в полі з файлом. Файли ігноруються — віджет надсилає лише текст.
- Справжнє поле називається
company_site. Це ім’я пастки; заявку вважають ботом. - Ваш власний обробник
crm:lead-beforeвикликавpreventDefault()— можливо, не тоді, коли ви очікували. - На цій сторінці немає віджета. Без
widget.jsатрибут нічого не робить, і форма надсилається по-старому (або не надсилається взагалі). - Його блокує Content Security Policy. Якщо на вашому сайті є CSP, у ній потрібні
script-src https://widget.sitecog.comіconnect-src https://back.sitecog.com. Консоль браузера скаже про це червоним. - Домен не є сайтом у CRM. Сайт розпізнається за origin сторінки (
www.відкидається). Тест наlocalhostчи на тестовому домені, якого не додано в CRM, даєunknown_domainна вкладці Network. - Ви дуже багато тестували. 20 пропусків форми за хвилину з одного IP — це вдосталь для людей, але не для шаленого клацання. Зачекайте хвилину.
Поле не дійшло
Заявка в CRM є, а одного поля в ній немає? Перевірте:
- Ім’я поля чутливе.
promo_passне дійде:passтут окреме слово. Перейменуйте наpromo_code— і все запрацює. Повний список — у розділі «Поля, які ніколи не надсилаються». - Поле або його обгортка має
data-crm-ignore. Атрибут наfieldsetчиdivвимикає все, що всередині. А якщо виключено хоч одне поле з цим ім’ям, решта з таким самим ім’ям теж не підуть. - Це
type="password". Такі поля не надсилаються ніколи. - Це поле з файлом. Файли ігноруються в будь-якому разі — віджет надсилає лише текст.
Сума не відображається
Позначте «Подія приносить гроші» в типі події. А ще перевірте, що сума — ціле число в мінімальних одиницях: 1499.00 треба надсилати як 149900.
Сповіщення в Telegram
Заявка, якої ніхто не побачив, — втрачена заявка. У налаштуваннях CRM → Заявки можна підключити Telegram-бота: вкажіть токен бота, увімкніть сповіщення, вирішіть, чи надсилати також підозрілі заявки, і задайте тихі години, щоб нічна зміна ботів не будила ваш відділ продажів.
Сповіщення оформлені HTML-розміткою, а все, що ввів відвідувач, перед надсиланням екранується. «Ім’я» на кшталт <a href="…">Натисніть сюди</a> прийде звичайним текстом: підсунути посилання в чат вашої команди чи зламати оформлення відвідувач не зможе. Довгі поля обрізаються, щоб роман у полі «Повідомлення» не перетворився на простирадло, — заявка повністю завжди є в CRM.
Видалення даних людини на запит
«Видаліть усе, що у вас є про мене» — звичайне прохання, і відповідь на нього не має перетворюватися на тиждень розкопок у таблицях. У CRM для цього є два інструменти, код із вашого боку не потрібен.
Із заявки або чату
У картці заявки й у картці чату є «Видалити дані відвідувача». Перед видаленням CRM показує, що саме буде видалено:
- профіль відвідувача та аналітика — перегляди сторінок і події;
- чати й вкладення — скільки чатів, повідомлень і файлів;
- заявки — лише якщо позначити «Також видалити заявки цього відвідувача». За замовчуванням вимкнено: заявка часто — угода в роботі, і вирішувати вам.
Заявка, надіслана із сервера без visitor_id, до відвідувача не прив’язана — у неї видаляється лише сама заявка.
За поштою або телефоном
Зазвичай прохання надходить листом: «Я anna@example.com, забудьте мене». Відкрийте CRM → Налаштування → «Запити на видалення даних», введіть пошту або телефон, які вказала людина, — і CRM знайде її заявки, чати та акаунт на вашому сайті. Одна кнопка видаляє все знайдене; разом із ним можна видалити й аналітику відвідувань пов’язаних відвідувачів.
- Права. Для видалення потрібні ті самі права, що й на редагування цих розділів. Менеджер, який може лише переглядати чати, побачить знайдене, але видалити не зможе.
- Журнал дій. Кожне видалення записується в журнал — без пошти й телефону людини, інакше журнал зберігав би саме те, що просили забути.
- Без скасування. Видалено — отже, видалено. Перевірте цифри, перш ніж підтверджувати.