LeadShield REST API
Базовый URL: https://lead-shield.ru/api/v1. Формат — JSON, авторизация — Bearer-токен. Ключ выдаётся после регистрации в личном кабинете.
Авторизация
Получите access-токен через POST /auth/login или POST /auth/register, затем передавайте его в заголовке:
Authorization: Bearer YOUR_ACCESS_TOKEN
Access-токен живёт ограниченное время; обновляется через POST /auth/refresh (refresh-токен — в httpOnly cookie). Выход — POST /auth/logout.
Верификация номеров
Основной сценарий из трёх шагов: инициализация → повторная отправка (при необходимости) → проверка кода.
Запускает верификацию: звонок Flash Call или SMS с кодом на указанный номер. Код живёт 5 минут.
Параметры: site_key (обязательный), phone в формате +7..., опционально mode (flash_sms, flash_telegram, sms, sms_flash_wcall, telegram_sms_wcall, vk_sms_wcall) и UTM-метки utm_source, utm_medium, utm_campaign, client_id, page_url.
curl -X POST https://lead-shield.ru/api/v1/verify/init \
-H 'Content-Type: application/json' \
-d '{
"site_key": "ls_pk_abc123",
"phone": "+79123456789"
}'
# 200 { "session_id": "...", "status": "code_sent", "channel": "flash_call" }
Повторная отправка кода. Не чаще 1 раза в 60 секунд на сессию. Параметр channel = sms или telegram — fallback, если Flash Call не дошёл.
curl -X POST https://lead-shield.ru/api/v1/verify/resend \
-H 'Content-Type: application/json' \
-d '{ "session_id": "SESSION_ID", "channel": "sms" }'
Проверка 4-значного кода, введённого пользователем. При успехе возвращает JWT-токен верификации (TTL 1 час).
curl -X POST https://lead-shield.ru/api/v1/verify/check \
-H 'Content-Type: application/json' \
-d '{ "session_id": "SESSION_ID", "code": "1234" }'
# 200 { "verified": true, "token": "..." } — либо { "verified": false, "attempts_left": 2 }
Сайты
| Метод | Описание |
|---|---|
| POST /sites | Создать сайт. Возвращает site_key и site_secret. |
| GET /sites | Список сайтов пользователя. |
| GET /sites/{id} | Детали сайта, включая site_secret. |
| PATCH /sites/{id} | Обновить сайт (название, домен, режим, webhook_url, is_active). |
| DELETE /sites/{id} | Удалить сайт. |
Подпись webhook
Каждый webhook (события verification.success и verification.failed на ваш webhook_url) подписан HMAC-SHA256 по секрету сайта site_secret (из личного кабинета). Всегда проверяйте подпись и freshness — иначе перехваченный запрос можно переотправить (replay).
| Заголовок | Значение |
|---|---|
| X-LeadShield-Timestamp | Время отправки, unix-seconds (UTC). |
| X-LeadShield-Signature | sha256=<hex> — HMAC-SHA256 от site_secret. |
Актуальный формат подписи
Подписывается строка f"{timestamp}.{raw_body}", где raw_body — тело запроса байт-в-байт как получено (без парсинга и пересериализации). Приёмник обязан отклонять запрос, если abs(now - timestamp) > 300 секунд (допуск ±5 минут против replay).
Переходный период (legacy-формат, 30 дней)
Запросы без X-LeadShield-Timestamp принимаются со старой подписью — HMAC-SHA256 от чистого тела: HMAC(site_secret, raw_body). Это обратная совместимость для уже развёрнутых приёмников: принимайте, но пишите deprecation-warning в лог и обновите проверку до актуального формата. Ориентировочное отключение legacy-формата — 21.11.2026 (30 дней с релиза, дату подтвердит владелец сервиса).
import hashlib, hmac, time
SECRET = b'ls_sk_0123456789abcdef0123456789abcdef' # site_secret из кабинета
MAX_SKEW_SEC = 300
def verify_signature(headers: dict, raw_body: bytes) -> bool:
ts = headers.get('X-LeadShield-Timestamp')
received = headers.get('X-LeadShield-Signature', '')
if not received.startswith('sha256='):
return False
if ts is not None:
# Актуальный формат: подпись от "{ts}.{body}", свежесть ±300 сек
if abs(time.time() - int(ts)) > MAX_SKEW_SEC:
return False
signed = f'{ts}.'.encode() + raw_body
else:
# Legacy-формат (переходный период): подпись от чистого тела.
# Принимаем, но логируем deprecation-warning.
signed = raw_body
expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received.removeprefix('sha256='))
# Flask: raw_body = request.get_data()
const crypto = require('crypto');
const SECRET = 'ls_sk_0123456789abcdef0123456789abcdef'; // site_secret из кабинета
const MAX_SKEW_SEC = 300;
// rawBody — тело запроса байт-в-байт (express.raw({ type: 'application/json' }))
function verifySignature(headers, rawBody) {
const ts = headers['x-leadshield-timestamp'];
const received = headers['x-leadshield-signature'] || '';
if (!received.startsWith('sha256=')) return false;
let signed;
if (ts !== undefined) {
// Актуальный формат: "{ts}.{body}", свежесть ±300 сек
if (Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > MAX_SKEW_SEC) return false;
signed = Buffer.concat([Buffer.from(`${ts}.`), rawBody]);
} else {
// Legacy-формат (переходный период): подпись от чистого тела + deprecation-warning в лог
console.warn('[leadshield-webhook] legacy signature format used — update verifier');
signed = rawBody;
}
const expected = crypto.createHmac('sha256', SECRET).update(signed).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received.slice(7)));
}
Логи и статистика
| Метод | Описание |
|---|---|
| GET /sites/{id}/logs | Логи верификаций сайта: пагинация, фильтры. Номера маскируются (+7***89). |
| GET /sites/{id}/export | Экспорт подтверждённых лидов в CSV. |
| GET /sites/{id}/stats | Статистика сайта по дням. |
| GET /stats/overview | Сводная статистика по всем сайтам пользователя. |
Тарифы и оплата
| Метод | Описание |
|---|---|
| GET /billing/plans | Список тарифов с ценами и лимитами (публичный, без авторизации). |
| GET /billing/current | Текущий тариф пользователя. |
| POST /billing/checkout | Создать сессию оплаты выбранного тарифа. |
Актуальные тарифы: Trial — 0₽ (5 верификаций), Start — 1690₽/мес (30 верификаций), Pro — 3990₽/мес (100 верификаций), Business — 8990₽/мес (300 верификаций).
Коды ошибок
| Код | Значение |
|---|---|
| 400 | Ошибка валидации полей (неверный формат телефона, короткий пароль и т.п.). |
| 401 | Не авторизован или неверные учётные данные. |
| 403 | Нет доступа (например, неверный текущий пароль при смене). |
| 404 | Сайт или ресурс не найден. |
| 409 | Конфликт (например, email уже занят при регистрации). |
| 410 | Сессия верификации завершена или код истёк. |
| 429 | Превышен лимит запросов. Тело содержит retry_after — пауза в секундах до повтора. |
| 502 | Провайдер связи недоступен (provider_unavailable). Повторите запрос позже. |
Тело ошибки всегда в формате: { "error": "код_ошибки", "message": "Человекочитаемое описание" }.
Лимиты и правила
/verify/init: с одного IP — не более 5 запросов в час, на один номер — не более 3 в час./verify/resend: 1 запрос в 60 секунд на сессию./verify/check: ограниченное число попыток на сессию; при исчерпании — 429.- Код верификации действует 5 минут, затем сессия переходит в статус
expired.