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

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

Как подключить LemonAD к Keitaro: где взять API Token и ID оффера, рабочий api.php для отправки лидов, глобальный постбэк и таблица статусов сети и трекера…

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

Интеграция LemonAD с Keitaro работает в две стороны. Форма на вашем лендинге отправляет заявку в LemonAD по API и передаёт в ней ID клика из трекера. Когда колл-центр обработал лид, LemonAD присылает в Keitaro постбэк с этим ID и статусом, и конверсия записывается в ту кампанию, которая её принесла.

Ниже всё для настройки: где взять API Token и ID оффера, форма, готовый api.php, постбэк и таблица статусов. Адреса, поля и макросы сверены с документацией LemonAD и справкой Keitaro по постбэкам 1 октября 2026 года.

LemonAD в 2026 году: что проверить перед стартом

  • Сеть работает. Сайт limonad.com и страница для вебмастеров limonadcpa.com открываются, регистрация и вход идут через кабинет app.limonad.com. LemonAD называет себя партнёрской сетью и прямым рекламодателем: на limonadcpa.com заявлено больше 1500 офферов в нутре, красоте и товарке.
  • Домен lemonad.com к сети не относится. Он выставлен на продажу. Ищите сеть по адресу limonad.com.
  • Документация открыта без входа: разделы «Настройка API», «Настройка PostBack» и «Интеграция с Keitaro» на docs.limonad.com.
  • Выплаты по FAQ сети идут в понедельник, среду и пятницу, запрос нужно отправить до 16:00 по Москве. На limonadcpa.com при этом обещаны выплаты день в день, так что условия для своего аккаунта уточните у менеджера.

Из нашего опыта работы с LemonAD в 2024 году: отзывчивые менеджеры, высокие ставки на старте и широкий выбор гео, от Африки до СНГ. Неудобно было с гарантами: их согласовывали только на три дня, и срок приходилось продлевать. Как LemonAD выглядит рядом с другими сетями, мы разбирали в подборке CPA-сетей для новичков, а актуальные офферы сети собраны в каталоге офферов LemonAD.

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

  1. Клик из рекламы приходит в Keitaro, трекер создаёт для него subid.
  2. Keitaro показывает лендинг, загруженный в трекер архивом, и подставляет subid в скрытое поле формы вместо макроса {subid}.
  3. Посетитель отправляет форму, её принимает api.php.
  4. api.php отправляет лид в LemonAD и кладёт subid в поле clickid.
  5. LemonAD меняет статус лида и шлёт постбэк с макросом {clickid}. Keitaro находит клик и пишет конверсию: сначала Lead, потом Sale или Rejected.

Если вы льёте на лендинг LemonAD без правок, API писать не нужно: архивы из карточки оффера уже содержат lemon.php и success.php. Свой обработчик нужен, когда лендинг ваш, переведённый или собран на другом движке. Трекер под эту схему мы сравнивали в статье Keitaro, Binom или AIO.

Где взять API Token и ID оффера в LemonAD

  1. Войдите в кабинет app.limonad.com.
  2. Откройте «Профиль», раздел «Глобальный постбек и API», и скопируйте API Token. Это токен вебмастера, он уходит в заголовке X-Token.
  3. Откройте карточку нужного оффера и скопируйте его полный ID. В примере из документации он выглядит так: d6a27e89-b044-4ff1-ad42-1d373d95a9c8. Короткий номер из списка офферов не подойдёт.
  4. Поток создавать не нужно. В FAQ LemonAD прямо сказано: чтобы лить по API, потоки не нужны, хэша потока нет.

Параметры API LemonAD

Лид отправляется методом POST в формате JSON. Обязательные поля и заголовок взяты из инструкции LemonAD по отправке лидов.

ПолеОбязательноЧто передавать
Адресдаhttps://sendmelead.com/api/v3/lead/add, метод POST, тело JSON
X-Token (заголовок)даAPI Token вебмастера
nameдаимя клиента
phoneдателефон клиента
offerIdдаполный ID оффера
clickidдля постбэкаsubid из Keitaro, вернётся макросом {clickid}
ip, domainнетIP посетителя и адрес лендинга, есть в примере запроса
utm_sourceнет, но LemonAD проситглобальный источник: facebook, google, tiktok
utm_campaignнеткампания или связка
utm_mediumнетидентификатор байера
utm_content, utm_termнетLemonAD советует оставлять пустыми
externalWebmasterIdнетID вашего вебмастера, если вы сами партнёрская сеть
Таблица прокручивается вбок

В таблице полей API clickid не указан. Но в инструкции LemonAD для Keitaro ID клика передают именно в поле clickid, и тот же {clickid} стоит в постбэке. Поэтому обработчик ниже отправляет subid в этом поле.

Ответ API мы проверили 1 октября 2026 года. Сервер отвечает HTTP 202 и JSON вида {"localClickId":"…","result":"ok"}. Такой же ответ пришёл и на запрос с неверным токеном и пустыми полями. Значит, LemonAD ставит лид в очередь и проверяет его позже, и result=ok не гарантирует, что лид принят. После первой отправки обязательно найдите лид в статистике кабинета.

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

Главный файл лендинга должен называться 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}">
  <!-- LemonAD просит метки: источник, кампанию и байера -->
  <input type="hidden" name="utm_source" value="{utm_source}">
  <input type="hidden" name="utm_campaign" value="{utm_campaign}">
  <input type="hidden" name="utm_medium" value="{utm_medium}">
  <button type="submit">Заказать</button>
</form>

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

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

Метки LemonAD просит не просто так. Сеть передаёт их рекламодателю, и тот видит выкуп по каждому источнику. При плохом выкупе рекламодатель может отключить один источник, а не весь ваш трафик. Не дублируйте одно значение во все метки и не используйте в них пробелы: Keitaro кодирует значения макросов для URL.

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

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

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

// ===== Настройки =====
const API_URL      = 'https://sendmelead.com/api/v3/lead/add';
const API_KEY      = 'API_KEY';     // app.limonad.com: Профиль, "Глобальный постбек и API", API Token
const OFFER_ID     = 'OFFER_ID';    // полный ID оффера из карточки, вида d6a27e89-b044-4ff1-ad42-1d373d95a9c8
const UTM_SOURCE   = 'facebook';    // глобальный источник трафика, если он не пришёл с формы
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 = [
    'name'         => $name,
    'phone'        => $phone,
    'offerId'      => OFFER_ID,
    'clickid'      => $subid,               // вернётся в постбэке макросом {clickid}
    'ip'           => client_ip(),
    'domain'       => landing_url(),
    'utm_source'   => post_macro('utm_source') ?: UTM_SOURCE,
    'utm_medium'   => post_macro('utm_medium'),
    'utm_campaign' => post_macro('utm_campaign'),
    'utm_content'  => post_macro('utm_content'),
    'utm_term'     => post_macro('utm_term'),
];

$json   = (string) json_encode(compact_fields($lead), JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
$res    = http_post(API_URL, $json, [
    'Content-Type: application/json',
    'X-Token: ' . API_KEY,
]);
$answer = json_decode($res['body'], true);
// LemonAD сразу отвечает 202 и result=ok, а проверяет лид позже: итог смотрите в кабинете
$ok     = $res['code'] >= 200 && $res['code'] < 300 && is_array($answer) && ($answer['result'] ?? '') === 'ok';

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.

Что делает скрипт:

  • принимает только POST и проверяет имя (от двух символов) и телефон (от 7 до 15 цифр);
  • берёт subid из формы. Если Keitaro не подставил макрос, лид всё равно уходит, но без ID клика, и это видно в журнале;
  • не отправляет тот же телефон повторно 24 часа. LemonAD считает дублем номер, который пришёл на тот же оффер в течение суток, от любого вебмастера. В файле data/phones.json лежат HMAC-хеши, а не сами номера;
  • ждёт ответа не дольше 3 секунд. Keitaro требует, чтобы PHP локального лендинга укладывался в 3 секунды, и просит ставить таймауты на все вызовы curl;
  • пишет журнал data/leads-ГГГГ-ММ-ДД.log: маскированный телефон, subid, код и текст ответа API;
  • при успехе ведёт на success.php, при ошибке на error.php.

В примере имя и телефон передаются в адресе страницы «спасибо». Они могут остаться в истории браузера и журналах запросов; в рабочей конфигурации лучше передавать эти данные через серверную сессию. Готовые мультиязычные страницы есть у нас: страница «Спасибо за заказ» и страница ошибки.

Мы прогнали обработчик против локальной заглушки API: успешный лид, повтор номера, ответ с ошибкой, HTML-ошибка 500, ответ дольше 3 секунд, пустой телефон, GET-запрос и форма без subid. Во всех случаях скрипт ведёт на нужную страницу и пишет строку в журнал.

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

  1. Соберите архив: index.php, api.php, success.php, error.php, стили и картинки. Загрузите его в Keitaro в раздел «Офферы» как локальный оффер.
  2. Создайте тестовую кампанию с одним потоком без фильтров и добавьте в поток этот оффер.
  3. Откройте ссылку кампании и посмотрите исходный код страницы. В поле subid должен стоять ID клика, а не текст {subid}.
  4. Отправьте форму и откройте журнал в папке data. Нужна строка с "result":"sent" и кодом 202.
  5. Найдите лид в кабинете LemonAD. Только там видно, принят он или отфильтрован. Тестовые лиды лучше согласовать с менеджером, чтобы колл-центр не звонил впустую.

Постбэк LemonAD в Keitaro

Шаг 1. Партнёрская сеть в Keitaro

  1. В Keitaro откройте «Партнерские сети», нажмите «Создать» и в поле «Из шаблона» выберите LemonAD.
  2. Откройте сохранённый шаблон и скопируйте Postback URL.
  3. Замените в ссылке [ISO] на валюту аккаунта, например usd.

Готовая ссылка по инструкции Keitaro для LemonAD:

https://ВАШ_ДОМЕН/POSTBACK_KEY/postback?subid={clickid}&payout={payout}&status={status}&lead_status=lead&sale_status=sale&rejected_status=rejected,trash&currency=usd&from=LemonAD.com

ВАШ_ДОМЕН и POSTBACK_KEY возьмите в разделе «Настройки», пункт «Postback URL». Параметры subid и status обязательны: без них трекер постбэк проигнорирует. Параметры lead_status, sale_status и rejected_status учат Keitaro понимать статусы сети.

настройка постбека для партнерки LemonAD

Шаг 2. Глобальный постбэк в кабинете LemonAD

  1. Нажмите на логин справа вверху, откройте «Профиль» и раздел «Глобальный постбек и API».
  2. Нажмите «Добавить постбэк» и вставьте ссылку из Keitaro в поле URL.
  3. Отметьте статусы и задайте имена: новые лиды lead, подтверждённые sale, треш rejected, отклонённые rejected.
  4. Нажмите «Сохранить».
настройка постбека для cpa сети Лимонад

Постбэк уходит методом GET. Кроме {clickid}, {status} и {payout}, LemonAD отдаёт макросы {leadid}, {utmSource}, {utmCampaign}, {utmMedium}, {utmContent}, {utmTerm} и {globalSource}. Если доход нужен только после оплаты лида, используйте {payoutOrZero}: в других статусах он отдаёт 0. Полный список в разделе «Настройка PostBack».

Глобальный постбэк действует на все офферы. Локальный задаётся в карточке одного оффера. По дублям LemonAD по умолчанию постбэки не шлёт: их включают переключателем в том же разделе «Глобальный постбек и API».

Статусы LemonAD и Keitaro

Статус в LemonADЧто значитИмя в кабинетеКонверсия в Keitaro
Новый лидлид в работе у колл-центраleadLead, доход в холде
Подтверждённыйрекламодатель подтвердил заказsaleSale, подтверждённый доход
Отклонённыйклиент отказалсяrejectedRejected
Трешневалидные данные или дубльrejectedRejected
Оплаталид оплаченможно не включатьне нужен
Таблица прокручивается вбок

Если треш хочется видеть отдельно, назовите его trash. Он всё равно попадёт в Rejected: в ссылке Keitaro стоит rejected_status=rejected,trash. Для отдельной колонки в отчётах в Keitaro есть тип конверсии Trash и параметр trash_status.

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

  • В логе Keitaro «Click for subid REPLACE not found». В постбэк пришёл не ID клика. Проверьте, что в форме есть поле subid с макросом, а лендинг загружен в Keitaro локально. Лог открывается в разделе «Обслуживание», «Логи», «Postbacks».
  • «Postback status is not set» или «Conversion type not found». В кабинете LemonAD не заданы имена статусов, или они не совпадают с обучением в ссылке. Разбор ошибок есть в справке Keitaro.
  • Тестовый постбэк из кабинета не создаёт конверсию. Это нормально: сети не передают реальный subid в тестовых постбэках. Проверяйте настоящим лидом через ссылку кампании.
  • API ответил ok, а лида в кабинете нет. Проверьте токен и полный ID оффера. Ответ LemonAD не говорит о результате проверки.
  • Много треша. LemonAD отфильтровывает номера короче 6 или длиннее 15 символов. Сеть советует убрать ограничение на количество символов в поле и показать подсказку с кодом страны и числом цифр.
  • Старый обработчик с файлом leads.txt. Он хранит телефоны открытым текстом в папке лендинга, и файл можно скачать по прямой ссылке. Если такой файл есть на сервере, удалите его.
  • Вывод $_GET в форму без экранирования. Код вида <?= $_GET['utm_term'] ?> позволяет подставить на страницу чужой скрипт через ссылку. Используйте макросы Keitaro или htmlspecialchars.

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

Где взять API токен LemonAD?

В кабинете app.limonad.com: «Профиль», раздел «Глобальный постбек и API», поле API Token. Он передаётся в заголовке X-Token.

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

https://sendmelead.com/api/v3/lead/add, метод POST, тело в JSON. Обязательные поля: name, phone и offerId.

Нужно ли создавать поток в LemonAD для работы по API?

Нет. По FAQ LemonAD для API потоки не нужны, лид привязывается к офферу через offerId.

Почему в Keitaro нет конверсий от LemonAD?

Чаще всего в форме нет поля с {subid} или лендинг открыт не через трекер. Откройте лог постбэков в Keitaro: если записей нет, постбэк не настроен в LemonAD, если есть ошибка, она укажет на причину.

Чем глобальный постбэк LemonAD отличается от локального?

Глобальный работает для всех офферов и задаётся в профиле. Локальный работает только для одного оффера и задаётся в его карточке.

Можно ли не писать свой api.php?

Да, если берёте лендинг из карточки оффера: в архиве уже есть lemon.php. В форму нужно добавить скрытое поле clickid со значением {subid} и UTM-метки, как в инструкции LemonAD для Keitaro.

Редакция BoostClicks

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