Настройка
В личном кабинете откройте квиз → «Настройки» → «Интеграции» → карточка Webhooks. Укажите URL, на который будет отправляться POST-запрос.
Для теста можно использовать webhook.site.
Адресов может быть несколько: заявка уйдёт на каждый.
Важно: обработчик должен отвечать успешным HTTP-статусом (200, 204 и т.п.). Если адрес не ответил, LeadForms повторит отправку (см. «Подпись и повторы» ниже).
Формат запроса
Метод:
Заголовок:
Тело:
POSTЗаголовок:
Content-Type: application/jsonТело:
JSON
| Заголовок | Значение |
|---|---|
X-Leadforms-Event | lead.created |
X-Leadforms-Delivery | Номер доставки. При повторе тот же: по нему отсеивайте дубли |
X-Leadforms-Attempt | Номер попытки, от 1 до 6 |
X-Leadforms-Signature | Подпись запроса t=<время>,v1=<подпись> |
User-Agent | LeadForms-Webhooks/1.0 |
Старые поля сохранены без изменений. Новые поля добавляются только если они есть в заявке — интеграции, завязанные на старый формат, продолжат работать.
Пример полного payload
{
"id": 12345,
"quizId": 611,
"answers": [
{"q": "Какая услуга нужна?", "questionId": 10, "a": {"105575": "Ремонт кухни"}},
{"q": "Что ещё важно?", "questionId": 11, "a": {"105580": "Срок", "custom": "Хочу успеть до Нового года"}},
{"q": "Площадь, м²", "questionId": 12, "a": 45}
],
"contacts": {
"name": "Иван",
"phone": "79991234567",
"email": "example@gmail.com",
"address": "Москва, ул. Примерная, 1",
"text": "Нужна консультация",
"messenger": {"type": "telegram", "value": "@username"},
"messengerName": "Иван"
},
"created": "17.08.2019 22:41",
"quizDurationSeconds": 95,
"result": {
"value": "15%",
"title": "Ваша скидка",
"description": "Завершите оформление"
},
"extra": {
"href": "https://example.com/page",
"ref": "https://google.com",
"utm": {"utm_source": "google"},
"cookies": {
"_ga": "GA1.2.477745229.1562102022",
"_ym_uid": "1562107156134812542"
},
"device": "mobile",
"client": {"os": "iOS", "browser": "Safari"}
},
"version": 1
}
Описание полей: верхний уровень
| Поле | Тип | Описание |
|---|---|---|
answers | array | Ответы на вопросы квиза |
contacts | object | Контактные данные |
created | string | Дата заявки, формат дд.мм.гггг чч:мм |
extra | object | UTM, cookies, URL страницы, referer, скидка |
result | object | null | Результат квиза (скидка и т.п.) |
id | number | ID заявки в LeadForms новое |
quizId | number | ID квиза, страницы или виджета новое |
quizDurationSeconds | number | Время прохождения квиза в секундах новое |
booking | object | Запись на встречу: startsAt, endsAt (UTC), timezone, visitorTimezone, status. Только у заявок с записью новое |
payment | object | Предоплата: status, amount, currency, provider, paidAt (UTC), refundedAmount, test. Только у заявок с оплатой новое |
version | number | Версия формата, сейчас 1. Поля выше не меняются; несовместимые изменения выйдут только новой версией новое |
Поля answers[]
| Поле | Описание |
|---|---|
q | Текст вопроса |
questionId | ID вопроса |
a | Ответ. Выбор вариантов: объект, где ключ это номер варианта, а значение его текст (свой ответ посетителя приходит под ключом custom). Поле ввода, число, ползунок: строка или число |
Поля contacts
| Поле | Описание |
|---|---|
name | Имя |
phone | Телефон |
email | |
address | Адрес новое |
text | Комментарий / произвольный текст новое |
messenger | Мессенджер: {"type": "telegram", "value": "@username"} новое |
messengerName | Имя при сборе через мессенджер новое |
Типы
messenger.type: telegram, whatsapp, viber, vk, messenger, instagram, teams, max, wechat, phone.Поля result
| Поле | Описание |
|---|---|
value | Значение (например, размер скидки) |
title | Заголовок |
description | Описание |
Поля extra
В
extra могут появляться новые ключи (например, рекламные идентификаторы): не отклоняйте заявку из-за незнакомого поля.| Поле | Описание |
|---|---|
href | URL страницы, с которой отправлена заявка |
ref | Referer |
utm | UTM-метки (объект) |
cookies | Cookies (объект) |
discount | Скидка, если настроена в квизе |
device | Устройство: mobile или desktop |
client | Система и браузер: {"os": "iOS", "browser": "Safari"} |
Подпись и повторы
Каждый запрос подписан, чтобы вы принимали заявки только от LeadForms. Секрет подписи у каждого адреса свой: в окне Webhooks нажмите кнопку с ключом рядом с адресом. Кнопка «Перевыпустить» выдаёт новый секрет, старый перестаёт подходить сразу.
Подпись
v1 = HMAC-SHA256 от строки <t>.<тело запроса как пришло> с вашим секретом, в шестнадцатеричном виде. Отклоняйте запросы с неверной подписью и с t старше 5 минут. Готовый код проверки на PHP, Node.js и Python: в документации API.| Ваш ответ | Что делает LeadForms |
|---|---|
| 2xx, 3xx | Заявка доставлена (на редиректы не переходим) |
| 408, 429, 5xx, нет ответа за 15 секунд, сетевая ошибка | Повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов |
| Другие 4xx | Ошибка без повтора |
Если заявка не дошла, вы увидите это в личном кабинете: колокольчик и плашка в интеграциях квиза.
Двухэтапная форма контактов
Если в квизе включена двухэтапная форма:
• Webhook уходит после полной заявки (когда собраны все шаги).
• Если пользователь заполнил только первый шаг и не вернулся — заявка может уйти через ~60 секунд с частичными контактами.
Пример PHP-обработчика
<?php
$body = file_get_contents('php://input');
// Проверка подписи: секрет из окна Webhooks (кнопка с ключом).
$secret = 'whsec_…';
$header = $_SERVER['HTTP_X_LEADFORMS_SIGNATURE'] ?? '';
$t = null; $sigs = [];
foreach (explode(',', $header) as $part) {
[$k, $v] = array_pad(explode('=', trim($part), 2), 2, '');
if ($k === 't') { $t = (int) $v; } elseif ($k === 'v1') { $sigs[] = $v; }
}
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
$valid = $t !== null && abs(time() - $t) <= 300
&& array_filter($sigs, fn ($s) => hash_equals($expected, $s));
if (!$valid) {
http_response_code(401);
exit;
}
$data = json_decode($body, true);
$answers = $data['answers'] ?? [];
$contacts = $data['contacts'] ?? [];
$phone = $contacts['phone'] ?? null;
$email = $contacts['email'] ?? null;
$name = $contacts['name'] ?? ($contacts['messengerName'] ?? null);
$messenger = $contacts['messenger'] ?? null; // ['type' => 'telegram', 'value' => '@username']
$address = $contacts['address'] ?? null;
$comment = $contacts['text'] ?? null;
// created в формате дд.мм.гггг чч:мм
$created = DateTime::createFromFormat('d.m.Y H:i', $data['created'] ?? '');
$utm = $data['extra']['utm'] ?? [];
$result = $data['result'] ?? null;
$quizDurationSeconds = $data['quizDurationSeconds'] ?? null;
$leadId = $data['id'] ?? null;
$quizId = $data['quizId'] ?? null;
// Ответы: $answer['a'] — объект «номер варианта => текст» или строка/число
foreach ($answers as $answer) {
$text = is_array($answer['a']) ? implode(', ', $answer['a']) : (string) $answer['a'];
// $answer['q'] — вопрос, $text — ответ
}
// Ваша логика обработки...
http_response_code(200);
exit;
Обратная совместимость
Интеграции, которые читают только
answers, contacts.name/phone/email, created, extra — продолжают работать. Новые поля (address, text, messenger, quizDurationSeconds, id, quizId, result, booking, payment, version) можно подключать по необходимости. Если нужны заявки всех квизов аккаунта сразу или забирать их по расписанию, смотрите API для разработчиков.