Перейти к содержанию

Интеграция Leadbit с Keitaro: API и постбэк

Интеграция Leadbit с Keitaro в 2026 году: адрес API wapi.leadbit.com, flow_hash, рабочий api.php, постбэки на каждый статус и таблица статусов…

Интеграция Leadbit с Keitaro: API и постбэк

Чтобы подключить Leadbit к Keitaro по API, нужны три вещи: обработчик формы, который отправляет лид на wapi.leadbit.com с ID клика в поле sub1, постбэк Leadbit с макросом {sub1} и соответствие статусов сети статусам трекера. Ниже рабочий api.php, форма, ссылки постбэка и таблица статусов.

Важная оговорка. Публичной документации API у Leadbit нет, описание доступно в кабинете после регистрации. Мы собрали то, что подтверждают открытые источники и проверка адреса API 1 октября 2026 года, а всё неподтверждённое пометили прямо в тексте.

Leadbit в 2026 году: что изменилось

  • Сайт работает, но позиционирование другое. leadbit.com теперь называет себя международным performance-агентством, которое соединяет бренды и команды трафика. Вертикали на главной: e-commerce, лидогенерация, финансы, health & beauty, загрузки.
  • Регистрация вебмастеров есть: кнопка Sign up, вариант I’m a webmaster. Русской версии сайта нет, адрес leadbit.com/ru/ отдаёт 404.
  • API для лидов отвечает. Адрес https://wapi.leadbit.com/api/pub/new-order/ принимает POST и возвращает JSON с ошибками проверки полей. Это подтверждает доступность адреса, но не успешное создание заказа с вашим ключом.

В 2024 году мы работали с Leadbit как с CPA-сетью с инхаус-офферами под разные гео, с нормальной поддержкой и выплатами без задержек. Какие офферы и ставки доступны сейчас, уточняйте у менеджера после регистрации. Если нужна сеть с открытой документацией, посмотрите нашу подборку CPA-сетей для новичков.

Как работает связка Leadbit и Keitaro

  1. Клик приходит в Keitaro, трекер создаёт subid и показывает ваш лендинг.
  2. Keitaro подставляет subid в скрытое поле формы вместо {subid}.
  3. api.php отправляет лид в Leadbit и кладёт subid в sub1.
  4. Leadbit шлёт постбэк на каждое событие: новый лид, подтверждение, отклонение, невалидный и треш. В постбэке {sub1} возвращает ID клика, и Keitaro пишет конверсию в нужную кампанию.

Если вы льёте на лендинг Leadbit по ссылке потока, API не нужен: хватит постбэка и шаблона Leadbit в трекере. API нужен для своего лендинга или прелендинга с формой. Как выбрать трекер под такую схему, мы разбирали в статье Keitaro, Binom или AIO.

Где взять API-ключ, хеш потока и лендинг

  1. Зарегистрируйтесь на leadbit.com как вебмастер и войдите в кабинет.
  2. В разделе «ИСТОЧНИКИ» создайте источник трафика.
  3. Создайте поток: в «ОФФЕРЫ» нажмите «СОЗДАТЬ ПОТОК» справа от оффера или откройте «ПОТОК» и нажмите «СОЗДАТЬ ПОТОК». Укажите название и источник, в секции «ЛЕНДИНГ» отметьте лендинг и сохраните поток. Порядок шагов взят из инструкции Keitaro для Leadbit.
  4. Хеш потока для поля flow_hash: символы в ссылке потока сразу после домена. В справке AlterCPA это четыре символа.
  5. Значение landing: домен лендинга, который выбран в настройках потока.
  6. API-ключ возьмите в разделе API кабинета. Точное название пункта меню в открытых источниках не подтверждено, при сложностях спросите менеджера.

Параметры API Leadbit

Ключ передаётся не в заголовке, а в конце адреса. Тело запроса обычная форма (application/x-www-form-urlencoded). На JSON сервер отвечает, что обязательные поля не переданы: мы это проверили.

ПолеОбязательноЧто передавать
Адресдаhttps://wapi.leadbit.com/api/pub/new-order/API_KEY, метод POST
flow_hashда, проверяет APIхеш потока
referrerда, проверяет APIадрес, с которого пришёл посетитель
landingда по старым инструкциямдомен лендинга из потока
countryда по старым инструкциямкод страны оффера из двух букв
name, phoneдаимя и телефон
sub1для постбэкаsubid из Keitaro, вернётся макросом {sub1}
sub2…sub5нетсвои метки: источник, кампания, объявление
email, lastname, address, commentнетдополнительные данные клиента
Таблица прокручивается вбок

Что отвечает сервер, мы проверили запросами с заведомо неверным ключом. Без flow_hash и referrer приходит HTTP 400 и {"success":false,"message":"Invalid request parameters","errors":["flow_hash is required","referrer is required"]}. С полями, но с неверным ключом приходит {"status":"fail","message":"An error is occurred. It is not possible to process request."}. Формат успешного ответа публично не описан, поэтому обработчик считает ошибкой HTTP 4xx и 5xx, status=fail и success=false, а всё остальное успехом. Такую проверку нельзя считать подтверждением принятия заказа: неизвестный JSON тоже может пройти условие. До рабочего запуска сверьте положительный ответ с документацией кабинета и найдите тестовый лид в статистике.

Форма на лендинге

Главный файл лендинга назовите index.php или index.html: только в них Keitaro подставляет макросы. Каждой форме поставьте action="api.php", method="post" и добавьте скрытые поля:

<form action="api.php" method="post">
  <input type="text" name="name" placeholder="Имя" required minlength="2" maxlength="100">
  <input type="tel" name="phone" placeholder="Телефон" required>
  <!-- ID клика Keitaro: трекер подставит его вместо {subid} -->
  <input type="hidden" name="subid" value="{subid}">
  <input type="hidden" name="referrer" value="{referrer}">
  <!-- уйдут в sub2-sub4 -->
  <input type="hidden" name="utm_source" value="{utm_source}">
  <input type="hidden" name="utm_campaign" value="{utm_campaign}">
  <input type="hidden" name="utm_content" value="{utm_content}">
  <button type="submit">Заказать</button>
</form>

Если лендинг лежит не в Keitaro, а на вашем хостинге, допишите к его ссылке в трекере ?subid={subid}, а в форме читайте параметр из адреса с экранированием:

<!-- ссылка на лендинг в Keitaro: https://ВАШ_ЛЕНДИНГ/?subid={subid} -->
<input type="hidden" name="subid" value="<?= htmlspecialchars($_GET['subid'] ?? '', ENT_QUOTES) ?>">

PHP-обработчик api.php для Leadbit

Положите файл рядом с index.php и заполните константы API_KEY, FLOW_HASH, LANDING и COUNTRY. Нужен PHP 7.2 или новее с curl и mbstring.

<?php
// api.php: принимает заявку с формы лендинга и отправляет лид в Leadbit по API
declare(strict_types=1);

// ===== Настройки =====
const API_URL      = 'https://wapi.leadbit.com/api/pub/new-order/';
const API_KEY      = 'API_KEY';     // API-ключ из кабинета Leadbit, дописывается в конец адреса
const FLOW_HASH    = 'FLOW_HASH';   // хеш потока: символы в ссылке потока сразу после домена
const LANDING      = 'LANDING';     // домен лендинга, выбранного в настройках потока
const COUNTRY      = 'ES';          // код страны оффера, две буквы
const SUCCESS_PAGE = 'success.php';
const ERROR_PAGE   = 'error.php';
const HTTP_TIMEOUT = 3;             // секунды: Keitaro ждёт от PHP локального лендинга не дольше 3 с
const DEDUP_TTL    = 86400;         // не отправлять тот же телефон повторно 24 часа
const DATA_DIR     = __DIR__ . '/data';

function go(string $page, array $query = []): void
{
    $query = array_filter($query, 'strlen');
    header('Location: ' . $page . ($query ? '?' . http_build_query($query) : ''), true, 303);
    exit;
}

function post_str(string $key, int $max = 255): string
{
    $v = isset($_POST[$key]) && is_string($_POST[$key]) ? trim(strip_tags($_POST[$key])) : '';
    return mb_substr($v, 0, $max);
}

function client_ip(): string
{
    // За Cloudflare в REMOTE_ADDR адрес узла Cloudflare, реальный IP посетителя в CF-Connecting-IP
    foreach (['HTTP_CF_CONNECTING_IP', 'REMOTE_ADDR'] as $k) {
        $ip = $_SERVER[$k] ?? '';
        if (filter_var($ip, FILTER_VALIDATE_IP)) {
            return $ip;
        }
    }
    return '';
}

function landing_url(): string
{
    $https = ($_SERVER['HTTPS'] ?? '') === 'on' || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https';
    $host  = preg_replace('/[^A-Za-z0-9.:-]/', '', $_SERVER['HTTP_HOST'] ?? '');
    return ($https ? 'https' : 'http') . '://' . $host . '/';
}

function post_macro(string $key): string
{
    // Keitaro подставляет значения макросов в URL-кодировке: пробел придёт как "+"
    return mb_substr(trim(urldecode(post_str($key, 1000))), 0, 255);
}

function referer_url(): string
{
    // Откуда пришёл посетитель: поле referrer формы ({referrer} в Keitaro), иначе адрес лендинга
    foreach ([post_macro('referrer'), (string) ($_SERVER['HTTP_REFERER'] ?? '')] as $ref) {
        if (filter_var($ref, FILTER_VALIDATE_URL)) {
            return mb_substr($ref, 0, 500);
        }
    }
    return landing_url();
}

function user_agent(): string
{
    return mb_substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255);
}

function compact_fields(array $fields): array
{
    return array_filter($fields, static function ($v): bool {
        return $v !== '';
    });
}

function data_file(string $name): string
{
    if (!is_dir(DATA_DIR) && @mkdir(DATA_DIR, 0750, true)) {
        @file_put_contents(DATA_DIR . '/.htaccess', "Require all denied\n");
    }
    return DATA_DIR . '/' . $name;
}

function log_event(array $row): void
{
    $line = json_encode(['time' => date('c')] + $row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
    @file_put_contents(data_file('leads-' . date('Y-m-d') . '.log'), $line . PHP_EOL, FILE_APPEND | LOCK_EX);
}

function mask_phone(string $digits): string
{
    return substr($digits, 0, 3) . '***' . substr($digits, -2);
}

// Защита от дублей: в файле хранятся не номера, а их HMAC, чтобы файл не стал базой телефонов
function phone_seen(string $digits, bool $remember = false): bool
{
    $fp = @fopen(data_file('phones.json'), 'c+');
    if ($fp === false) {
        return false; // нет прав на запись: лид не блокируем
    }
    flock($fp, LOCK_EX);
    $list = json_decode((string) stream_get_contents($fp), true);
    $list = is_array($list) ? $list : [];
    $now  = time();
    foreach ($list as $k => $t) {
        if ($now - (int) $t > DEDUP_TTL) {
            unset($list[$k]);
        }
    }
    $key  = hash_hmac('sha256', $digits, API_KEY);
    $seen = isset($list[$key]);
    if ($remember) {
        $list[$key] = $now;
    }
    ftruncate($fp, 0);
    rewind($fp);
    fwrite($fp, (string) json_encode($list));
    flock($fp, LOCK_UN);
    fclose($fp);
    return $seen;
}

function http_post(string $url, string $body, array $headers): array
{
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $body,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => HTTP_TIMEOUT,
        CURLOPT_TIMEOUT        => HTTP_TIMEOUT,
    ]);
    $answer = curl_exec($ch);
    return [
        'code'  => (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE),
        'body'  => is_string($answer) ? $answer : '',
        'error' => curl_error($ch),
    ];
}

// ===== Обработка заявки =====
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    go('index.php');
}
if (!function_exists('curl_init')) {
    log_event(['result' => 'no_curl']);
    go(ERROR_PAGE);
}

$name   = post_str('name', 100);
$phone  = (string) preg_replace('/[^\d+]/', '', post_str('phone', 32));
$digits = (string) preg_replace('/\D/', '', $phone);
$subid  = post_str('subid', 100);
$subid  = preg_match('/^[A-Za-z0-9._-]+$/', $subid) ? $subid : ''; // {subid} не подставился = пусто
$thanks = ['name' => $name, 'phone' => $phone, 'pxl' => (string) preg_replace('/\D/', '', post_str('pxl', 32))];

if (mb_strlen($name) < 2 || strlen($digits) < 7 || strlen($digits) > 15) {
    go(ERROR_PAGE);
}
if (phone_seen($digits)) {
    log_event(['result' => 'duplicate', 'phone' => mask_phone($digits), 'subid' => $subid]);
    go(SUCCESS_PAGE, $thanks);
}

$lead = [
    'flow_hash' => FLOW_HASH,
    'landing'   => LANDING,
    'country'   => COUNTRY,
    'name'      => $name,
    'phone'     => $phone,
    'referrer'  => referer_url(),
    'sub1'      => $subid,                  // вернётся в постбэке макросом {sub1}
    'sub2'      => post_macro('utm_source'),
    'sub3'      => post_macro('utm_campaign'),
    'sub4'      => post_macro('utm_content'),
];

$res    = http_post(API_URL . rawurlencode(API_KEY), http_build_query(compact_fields($lead)), [
    'Content-Type: application/x-www-form-urlencoded',
]);
$answer = json_decode($res['body'], true);
// Успешный ответ публично не описан: ошибкой считаем HTTP 4xx/5xx, status=fail и success=false
$ok     = $res['code'] >= 200 && $res['code'] < 300 && is_array($answer)
    && ($answer['status'] ?? '') !== 'fail' && ($answer['success'] ?? true) !== false;

log_event([
    'result' => $ok ? 'sent' : 'error',
    'phone'  => mask_phone($digits),
    'subid'  => $subid,
    'http'   => $res['code'],
    'curl'   => $res['error'],
    'answer' => mb_substr($res['body'], 0, 500),
]);

if (!$ok) {
    go(ERROR_PAGE);
}
phone_seen($digits, true);
go(SUCCESS_PAGE, $thanks);

Перед рабочим запуском. Проверка номера и запись после ответа API выполняются раздельно: одновременные запросы могут пройти оба. Для защиты от гонки нужна атомарная операция или поддерживаемый сетью ключ идемпотентности. Папку data держите вне публичного каталога: создаваемый .htaccess не защищает её в nginx. В журнал попадает ответ API, который может содержать персональные данные или секреты; маскируйте их до записи. Заголовку CF-Connecting-IP доверяйте только от проверенного прокси Cloudflare.

Чем этот код отличается от версии 2024 года из прошлой редакции статьи:

  • Проверяет ответ. Старый скрипт отправлял лид и вёл на страницу успеха, даже если Leadbit вернул ошибку. Теперь при ошибке посетитель попадёт на error.php, а причина останется в журнале data/leads-ГГГГ-ММ-ДД.log.
  • Работает по HTTPS. Старый адрес был http://, через HTTPS тот же адрес отвечает так же.
  • Не хранит телефоны открытым текстом. Защита от повторной отправки хранит HMAC-хеши номеров 24 часа, а журнал пишет маскированный номер. Зачем вообще отсекать дубли, мы писали в статье про дубли лидов.
  • Укладывается в 3 секунды. Это требование Keitaro к PHP локальных лендингов: на все вызовы curl нужны таймауты.
  • Берёт реальный IP за Cloudflare из заголовка CF-Connecting-IP.

При успехе скрипт ведёт на success.php и передаёт имя и телефон, чтобы клиент проверил данные. Готовые страницы: «Спасибо за заказ» и страница ошибки. Мы прогнали скрипт против локальной заглушки API (успех, повтор номера, ошибка, HTML 500, ответ дольше 3 секунд, пустой телефон, GET, форма без subid) и против настоящего адреса Leadbit с неверным ключом. Во всех случаях он ведёт на нужную страницу и пишет журнал.

Как проверить отправку

  1. Загрузите архив с index.php, api.php, success.php и error.php в Keitaro как локальный оффер.
  2. Создайте тестовую кампанию с одним потоком без фильтров и откройте её ссылку.
  3. В исходном коде страницы проверьте поле subid: там должен быть ID клика, а не {subid}.
  4. Отправьте форму и откройте журнал в папке data. При ошибке там будет полный ответ Leadbit.
  5. Найдите лид в статистике Leadbit и убедитесь, что у клика записан sub1.

Постбэк Leadbit в Keitaro

Шаг 1. Шаблон в Keitaro

В Keitaro откройте «Партнерские сети», нажмите «Создать», в поле «Из шаблона» введите Leadbit и сохраните. Так выглядел шаблон в нашем трекере в 2024 году:

Настройка API и постбеков в Кейтаро для Leadbit

Ссылка из этого шаблона с одним общим постбэком и обучением статусов:

https://ВАШ_ДОМЕН/POSTBACK_KEY/postback?subid={sub1}&payout={cost}&status={status}&currency=usd&tid={id}&offer_landing={landing}&flow={flow}&lead_status=new&sale_status=confirm&rejected_status=reject,decline,invalid,trash&from=leadbit.com

Шаг 2. Постбэки в кабинете Leadbit

В кабинете Leadbit у постбэка отдельные поля под каждое событие: при новом лиде, при подтверждении, при отклонении, на невалидный лид, на треш лид и при новой регистрации. Поэтому проще всего в каждое поле поставить готовый статус Keitaro. Обучение статусов тогда не нужно:

Postback при новом лиде:
https://ВАШ_ДОМЕН/POSTBACK_KEY/postback?subid={sub1}&status=lead&payout={cost}¤cy=usd&tid={id}

Postback при подтверждении лида:
https://ВАШ_ДОМЕН/POSTBACK_KEY/postback?subid={sub1}&status=sale&payout={cost}¤cy=usd&tid={id}

Postback при отклонении, на невалидный и на треш лид:
https://ВАШ_ДОМЕН/POSTBACK_KEY/postback?subid={sub1}&status=rejected&payout={cost}&currency=usd&tid={id}

ВАШ_ДОМЕН и POSTBACK_KEY возьмите в Keitaro в разделе «Настройки», пункт «Postback URL». Поле «при новой регистрации» для нутры оставьте пустым. В tid уходит ID лида в Leadbit: тогда новые статусы одного лида обновляют его конверсию, а два лида с одного клика не перезаписывают друг друга. Так работает параметр tid по справке Keitaro.

настройка постбеков в кейтаро с ПП leadbit.com

Где именно лежат эти поля в 2026 году, публично не описано. По скриншоту 2024 года это «Редактировать глобальный постбэк», по инструкции Keitaro поля «НАСТРОЙКА ПЕРЕДАЧИ СТАТИСТИКИ» есть и в настройках потока, а справка Voluum указывает путь Profile, Global Postback. Выберите один вариант, глобальный постбэк или поля в потоке, чтобы потом не искать, откуда пришла конверсия.

Макросы постбэка Leadbit по шаблонам трекеров AnyTrack и Voluum: {sub1}…{sub5}, {cost} (выплата), {currency}, {status}, {id}, {flow}, {landing}, {offer}, {ip}, {date}.

Статусы Leadbit и Keitaro

Событие в LeadbitЗначение status в шаблоне KeitaroЧто ставить в отдельное полеКонверсия в Keitaro
Новый лидnewstatus=leadLead
Подтверждениеconfirmstatus=saleSale
Отклонениеdecline, rejectstatus=rejectedRejected
Невалидный лидinvalidstatus=rejectedRejected
Трешtrashstatus=rejectedRejected
Новая регистрацияне указанооставить пустымне нужна для нутры
Таблица прокручивается вбок

Частые ошибки

  • Ответ «flow_hash is required», хотя поле есть. Лид ушёл в JSON. Leadbit ждёт обычную форму, как в коде выше.
  • Ответ «It is not possible to process request». Неверный ключ, хеш чужого потока или лендинг не из этого потока.
  • В Keitaro «Click for subid REPLACE not found». Leadbit не получил subid: в форме нет поля с {subid} или лендинг открыт не через трекер.
  • «Conversion type not found». В постбэк пришёл статус, которого трекер не знает. Добавьте его в lead_status, sale_status или rejected_status. Остальные коды ошибок разобраны в справке Keitaro.
  • Тестовый постбэк не создаёт конверсию. В тестовых постбэках сети не передают настоящий subid. Проверяйте реальным лидом.
  • Файл leads.txt со старым скриптом. В нём телефоны открытым текстом в папке лендинга. Удалите его с сервера.

Частые вопросы

Работает ли Leadbit в 2026 году?

Да. Сайт leadbit.com открывается, регистрация вебмастеров есть, адрес API отвечает. Сама компания теперь называет себя performance-агентством, а не CPA-сетью.

Где документация API Leadbit?

Только в кабинете после регистрации. Открытой документации нет, поэтому сверяйте поля с разделом API кабинета или у менеджера.

Что такое flow_hash в Leadbit?

Это хеш потока: символы в ссылке потока сразу после домена. Без него API отвечает ошибкой «flow_hash is required».

Какой адрес API у Leadbit?

https://wapi.leadbit.com/api/pub/new-order/, к нему в конце добавляется ваш API-ключ. Метод POST, данные обычной формой.

Почему не приходят постбэки от Leadbit?

Проверьте, что ссылки стоят во всех полях постбэка, а в лиде записан sub1. Сеть может отправить постбэк не сразу, а после обработки лида колл-центром.

Редакция BoostClicks

Материалы о настройке рекламы, трекинге и автоматизации: инструкции, примеры кода и расчёты с указанием условий проверки.