Что умеет API
Заявки. Забирать новые заявки по порядку или получать каждую сразу на свой адрес (подписка). Так работают наши приложения для Zapier, Make и n8n.
Чтение кабинета. Квизы, страницы, виджеты, проекты и аналитика доступны по ключу на чтение.
Изменения квизов, страниц и виджетов по ключу делаются только через MCP-сервер: его инструменты меняют ровно то, что нужно, и не затирают остальное. Как подключить ассистента: LeadForms в ИИ-агенте.
Чтение кабинета. Квизы, страницы, виджеты, проекты и аналитика доступны по ключу на чтение.
Изменения квизов, страниц и виджетов по ключу делаются только через MCP-сервер: его инструменты меняют ровно то, что нужно, и не затирают остальное. Как подключить ассистента: LeadForms в ИИ-агенте.
Адрес и ключ доступа
Адрес API:
Ключ создаётся в личном кабинете: меню → «ИИ-агенты и API». Он выглядит как
https://api.leadforms.ru (для аккаунтов leadforms.org: https://api.leadforms.org, ключ подходит к обоим).Ключ создаётся в личном кабинете: меню → «ИИ-агенты и API». Он выглядит как
lfk_… и показывается один раз, сохраните его сразу. Передавайте ключ в каждом запросе заголовком:Authorization: Bearer ВАШ_КЛЮЧ
Проверить ключ:
GET /v1/me вернёт {id, email, name, brand, locale}.curl -H "Authorization: Bearer ВАШ_КЛЮЧ" "https://api.leadforms.ru/v1/me"
Назначение ключа = его права
| Назначение в кабинете | Что открывает |
|---|---|
| ИИ-агент (MCP) | MCP-сервер (создание и правка квизов, страниц, виджетов, работа с заявками), чтение кабинета, лента заявок |
| Zapier / Make / n8n | Только заявки: лента, пример заявки, список квизов, подписки |
| Своя интеграция | Всё перечисленное: MCP, чтение кабинета, лента и подписки |
По ключу недоступны никому: удаление квизов и заявок, оплата, профиль, передача квизов в другой аккаунт и сами ключи. Это делается только в кабинете.
Если ключ мог попасть к посторонним, нажмите «Отозвать» рядом с ним в кабинете: он перестанет работать сразу, а подписки, созданные этим ключом, отключатся.
Если ключ мог попасть к посторонним, нажмите «Отозвать» рядом с ним в кабинете: он перестанет работать сразу, а подписки, созданные этим ключом, отключатся.
Лента новых заявок
GET /v1/leads?since_id=0&limit=50 отдаёт заявки по порядку поступления после заявки с номером since_id. Первый раз передайте since_id=0, дальше номер последней полученной.curl -H "Authorization: Bearer ВАШ_КЛЮЧ" "https://api.leadforms.ru/v1/leads?since_id=0&limit=10"
| Параметр | Описание |
|---|---|
since_id | Номер последней полученной заявки, 0 для начала с самой первой |
limit | Сколько заявок вернуть, от 1 до 100, по умолчанию 50 |
quiz_ids | Только заявки этих квизов, страниц или виджетов: 6903,7012. Без параметра все |
order | asc (по умолчанию) или desc: сначала новые, для «последних N» |
Ответ:
Номера заявок не идут подряд, но порядок ленты держит сервер: ни одна заявка не пропадёт между запросами.
{"data": [заявки…], "meta": {"since_id", "next_since_id", "has_more", "limit"}}. Следующий запрос делайте с since_id = meta.next_since_id, пока has_more равно true.Номера заявок не идут подряд, но порядок ленты держит сервер: ни одна заявка не пропадёт между запросами.
GET /v1/leads/sample вернёт последнюю заявку или пример того же вида, если заявок ещё нет (удобно для настройки сценария).GET /v1/leads/sources вернёт квизы, страницы и виджеты аккаунта: {"data": [{"id", "name", "type", "published"}]}.Подписка: каждая заявка сразу на ваш адрес
Подпишите свой адрес, и LeadForms будет отправлять на него каждую новую заявку запросом
POST.curl -X POST -H "Authorization: Bearer ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
-d '{"target_url":"https://example.com/leadforms","quiz_ids":[6903]}' \
"https://api.leadforms.ru/v1/hooks"
| Поле | Описание |
|---|---|
target_url | Обязательно. Адрес http или https, доступный из интернета |
quiz_ids | Только эти квизы, страницы или виджеты. Без поля все объекты аккаунта |
source | api (по умолчанию), zapier, make или n8n: подпись в кабинете |
Ответ содержит
id подписки и secret вида whsec_… для проверки подписи. Секрет отдаётся только здесь, сохраните его. Повторная подписка того же адреса обновит настройки.GET /v1/hooks: список подписок. DELETE /v1/hooks/{id}: отписаться. Подписка снимается и сама, если ваш адрес ответит 410 Gone, или если отозвать ключ, которым она создана. Не больше 100 подписок на аккаунт.Как приходит заявка на ваш адрес
Запрос
POST с телом JSON (формат ниже) и заголовками:| Заголовок | Значение |
|---|---|
X-Leadforms-Event | lead.created |
X-Leadforms-Delivery | Номер доставки. При повторе тот же: по нему отсеивайте дубли |
X-Leadforms-Attempt | Номер попытки, от 1 до 6 |
X-Leadforms-Signature | t=<время>,v1=<подпись> |
| Ваш ответ | Что делает LeadForms |
|---|---|
| 2xx | Заявка доставлена |
| 408, 429, 5xx, нет ответа за 15 секунд, сетевая ошибка | Повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов |
| 410 Gone | Подписка снимается |
| Другие 4xx | Ошибка без повтора, видна в кабинете |
Отвечайте быстро, а тяжёлую обработку делайте после ответа. Если заявка не дошла, владелец аккаунта увидит это в кабинете.
Проверка подписи
Подпись
v1 = HMAC-SHA256 от строки <t>.<тело запроса как пришло> с секретом подписки, в шестнадцатеричном виде. Отклоняйте запросы с неверной подписью и с t старше 5 минут. Если в заголовке несколько v1=, подходит любая.PHP
function leadformsVerify(string $secret, string $body, string $header, int $tolerance = 300): bool
{
$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; }
}
if ($t === null || abs(time() - $t) > $tolerance) {
return false;
}
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
foreach ($sigs as $sig) {
if (hash_equals($expected, $sig)) {
return true;
}
}
return false;
}
$body = file_get_contents('php://input');
if (!leadformsVerify('whsec_…', $body, $_SERVER['HTTP_X_LEADFORMS_SIGNATURE'] ?? '')) {
http_response_code(401);
exit;
}
$lead = json_decode($body, true);
Node.js (тело нужно сырым, до разбора JSON)
const crypto = require('crypto');
function leadformsVerify(secret, rawBody, header, tolerance = 300) {
let t = null; const sigs = [];
for (const part of String(header || '').split(',')) {
const [k, v] = part.trim().split('=');
if (k === 't') t = Number(v); else if (k === 'v1') sigs.push(v);
}
if (!t || Math.abs(Date.now() / 1000 - t) > tolerance) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return sigs.some((s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
}
app.post('/leadforms', express.raw({ type: 'application/json' }), (req, res) => {
if (!leadformsVerify(process.env.LEADFORMS_SECRET, req.body.toString('utf8'), req.get('X-Leadforms-Signature'))) {
return res.sendStatus(401);
}
const lead = JSON.parse(req.body);
res.sendStatus(200);
});
Python
import hashlib, hmac, time
def leadforms_verify(secret: str, body: bytes, header: str, tolerance: int = 300) -> bool:
t, sigs = None, []
for part in (header or '').split(','):
k, _, v = part.strip().partition('=')
if k == 't' and v.isdigit():
t = int(v)
elif k == 'v1':
sigs.append(v)
if t is None or abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f'{t}.'.encode() + body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, s) for s in sigs)
Формат заявки
Один и тот же в подписке, ленте, примере и вебхуках квиза.
{
"id": 241166955,
"quizId": 6903,
"answers": [
{"q": "Какая услуга вас интересует?", "questionId": 34092, "a": {"105575": "Консультация"}}
],
"contacts": {"name": "Иван", "phone": "+79991234567", "email": "ivan@example.com"},
"created": "21.10.2026 12:00",
"quizDurationSeconds": 95,
"result": null,
"extra": {
"href": "https://site.ru/",
"ref": "https://yandex.ru/",
"utm": {"utm_source": "yandex"},
"cookies": {},
"device": "mobile",
"client": {"os": "iOS", "browser": "Safari"}
},
"version": 1
}
| Поле | Описание |
|---|---|
id | Номер заявки, как в кабинете |
quizId | Номер квиза, страницы или виджета |
answers | Ответы: q текст вопроса, questionId номер вопроса, a выбранное: ключ это номер варианта (или custom для своего ответа), значение текст |
contacts | Контакты из формы: имя, телефон, почта и другие поля, которые есть в форме |
created | Дата заявки, дд.мм.гггг чч:мм |
quizDurationSeconds | Сколько секунд человек проходил квиз |
result | Результат квиза, если он есть, иначе null |
extra | Страница заявки, откуда пришёл, UTM-метки, устройство |
booking, payment | Только у заявок с записью на встречу или с оплатой |
version | Версия формата. Поля выше не меняются; несовместимые изменения выйдут только новой версией. В extra могут появляться новые ключи |
Отправляются только оплаченные заявки. Тестовые заявки владельца аккаунта тоже приходят: так удобно проверить сценарий.
Ошибки и лимиты
Ошибка приходит в виде
{"error": {"code": "…", "message": "…", "statusCode": 422}}, текст на языке кабинета. Лимит: 240 запросов в минуту на ключ.| Код | Что значит |
|---|---|
401 INVALID_API_KEY | Ключ не найден или отозван |
403 API_KEY_SCOPE | У ключа нет нужного права: создайте ключ с подходящим назначением |
403 API_KEY_USE_MCP | Квизы, страницы и виджеты по ключу меняются только через MCP |
403 API_KEY_FORBIDDEN | Действие доступно только в кабинете |
422 INVALID_CURSOR | Такой заявки нет в аккаунте: начните с since_id=0 |
422 UNKNOWN_QUIZ | В quiz_ids чужой или несуществующий номер |
422 LIMIT | Подписок на аккаунт уже 100 |
429 RATE_LIMIT | Слишком много запросов: подождите столько секунд, сколько в заголовке Retry-After |
Готовые подключения
Если писать код не нужно: в кабинете в разделе «ИИ-агенты и API» есть приложения для Zapier, Make и n8n, они уже умеют получать заявки. Для ИИ-ассистентов (Claude, ChatGPT, Cursor) подключите MCP-сервер. Простая отправка заявок одного квиза на ваш адрес: вебхуки квиза.
Не получается настроить?
Если что-то не получается, вы можете , поможем разобраться.