Главная Инструкции Как интегрировать квиз через Webhooks?

Как передавать заявки из квиза через вебхуки

Если вы используете свою CRM, приложение или сервисы, с которыми у нас ещё нет готовых интеграций, подойдёт интеграция через webhooks. На указанный URL LeadForms отправляет данные о новой заявке.
Настройка
В личном кабинете откройте квиз → «Настройки» → «Интеграции» → карточка Webhooks. Укажите URL, на который будет отправляться POST-запрос.
Для теста можно использовать webhook.site.
Адресов может быть несколько: заявка уйдёт на каждый.
Важно: обработчик должен отвечать успешным HTTP-статусом (200, 204 и т.п.). Если адрес не ответил, LeadForms повторит отправку (см. «Подпись и повторы» ниже).
Формат запроса
Метод: POST
Заголовок: Content-Type: application/json
Тело: JSON
ЗаголовокЗначение
X-Leadforms-Eventlead.created
X-Leadforms-DeliveryНомер доставки. При повторе тот же: по нему отсеивайте дубли
X-Leadforms-AttemptНомер попытки, от 1 до 6
X-Leadforms-SignatureПодпись запроса t=<время>,v1=<подпись>
User-AgentLeadForms-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
}
Описание полей: верхний уровень
ПолеТипОписание
answersarrayОтветы на вопросы квиза
contactsobjectКонтактные данные
createdstringДата заявки, формат дд.мм.гггг чч:мм
extraobjectUTM, cookies, URL страницы, referer, скидка
resultobject | nullРезультат квиза (скидка и т.п.)
idnumberID заявки в LeadForms новое
quizIdnumberID квиза, страницы или виджета новое
quizDurationSecondsnumberВремя прохождения квиза в секундах новое
bookingobjectЗапись на встречу: startsAt, endsAt (UTC), timezone, visitorTimezone, status. Только у заявок с записью новое
paymentobjectПредоплата: status, amount, currency, provider, paidAt (UTC), refundedAmount, test. Только у заявок с оплатой новое
versionnumberВерсия формата, сейчас 1. Поля выше не меняются; несовместимые изменения выйдут только новой версией новое
Поля answers[]
ПолеОписание
qТекст вопроса
questionIdID вопроса
aОтвет. Выбор вариантов: объект, где ключ это номер варианта, а значение его текст (свой ответ посетителя приходит под ключом custom). Поле ввода, число, ползунок: строка или число
Поля contacts
ПолеОписание
nameИмя
phoneТелефон
emailEmail
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 могут появляться новые ключи (например, рекламные идентификаторы): не отклоняйте заявку из-за незнакомого поля.
ПолеОписание
hrefURL страницы, с которой отправлена заявка
refReferer
utmUTM-метки (объект)
cookiesCookies (объект)
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 для разработчиков.

Остались вопросы? Напишите нам

Имя *
* Обязательно к заполнению
e-mail *
* Обязательно к заполнению
Сообщение
Напишите нам!
Ответим в течение 30 секунд