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

Интеграция LemonAD с Keitaro работает в две стороны. Форма на вашем лендинге отправляет заявку в LemonAD по API и передаёт в ней ID клика из трекера. Когда колл-центр обработал лид, LemonAD присылает в Keitaro постбэк с этим ID и статусом, и конверсия записывается в ту кампанию, которая её принесла.
Ниже всё для настройки: где взять API Token и ID оффера, форма, готовый api.php, постбэк и таблица статусов. Адреса, поля и макросы сверены с документацией LemonAD и справкой Keitaro по постбэкам 1 октября 2026 года.
- LemonAD в 2026 году: что проверить перед стартом
- Как работает связка LemonAD и Keitaro
- Где взять API Token и ID оффера в LemonAD
- Параметры API LemonAD
- Форма на лендинге
- PHP-обработчик api.php для LemonAD
- Как проверить отправку лида
- Постбэк LemonAD в Keitaro
- Шаг 1. Партнёрская сеть в Keitaro
- Шаг 2. Глобальный постбэк в кабинете LemonAD
- Статусы LemonAD и Keitaro
- Частые ошибки
- Частые вопросы
- Где взять API токен LemonAD?
- Какой адрес API у LemonAD?
- Нужно ли создавать поток в LemonAD для работы по API?
- Почему в Keitaro нет конверсий от LemonAD?
- Чем глобальный постбэк LemonAD отличается от локального?
- Можно ли не писать свой api.php?
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
- Клик из рекламы приходит в Keitaro, трекер создаёт для него subid.
- Keitaro показывает лендинг, загруженный в трекер архивом, и подставляет subid в скрытое поле формы вместо макроса
{subid}. - Посетитель отправляет форму, её принимает
api.php. api.phpотправляет лид в LemonAD и кладёт subid в полеclickid.- LemonAD меняет статус лида и шлёт постбэк с макросом
{clickid}. Keitaro находит клик и пишет конверсию: сначала Lead, потом Sale или Rejected.
Если вы льёте на лендинг LemonAD без правок, API писать не нужно: архивы из карточки оффера уже содержат lemon.php и success.php. Свой обработчик нужен, когда лендинг ваш, переведённый или собран на другом движке. Трекер под эту схему мы сравнивали в статье Keitaro, Binom или AIO.
Где взять API Token и ID оффера в LemonAD
- Войдите в кабинет app.limonad.com.
- Откройте «Профиль», раздел «Глобальный постбек и API», и скопируйте API Token. Это токен вебмастера, он уходит в заголовке
X-Token. - Откройте карточку нужного оффера и скопируйте его полный ID. В примере из документации он выглядит так:
d6a27e89-b044-4ff1-ad42-1d373d95a9c8. Короткий номер из списка офферов не подойдёт. - Поток создавать не нужно. В 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. Во всех случаях скрипт ведёт на нужную страницу и пишет строку в журнал.
Как проверить отправку лида
- Соберите архив:
index.php,api.php,success.php,error.php, стили и картинки. Загрузите его в Keitaro в раздел «Офферы» как локальный оффер. - Создайте тестовую кампанию с одним потоком без фильтров и добавьте в поток этот оффер.
- Откройте ссылку кампании и посмотрите исходный код страницы. В поле
subidдолжен стоять ID клика, а не текст{subid}. - Отправьте форму и откройте журнал в папке
data. Нужна строка с"result":"sent"и кодом 202. - Найдите лид в кабинете LemonAD. Только там видно, принят он или отфильтрован. Тестовые лиды лучше согласовать с менеджером, чтобы колл-центр не звонил впустую.
Постбэк LemonAD в Keitaro
Шаг 1. Партнёрская сеть в Keitaro
- В Keitaro откройте «Партнерские сети», нажмите «Создать» и в поле «Из шаблона» выберите LemonAD.
- Откройте сохранённый шаблон и скопируйте Postback URL.
- Замените в ссылке
[ISO]на валюту аккаунта, напримерusd.
Готовая ссылка по инструкции Keitaro для LemonAD:
https://ВАШ_ДОМЕН/POSTBACK_KEY/postback?subid={clickid}&payout={payout}&status={status}&lead_status=lead&sale_status=sale&rejected_status=rejected,trash¤cy=usd&from=LemonAD.comВАШ_ДОМЕН и POSTBACK_KEY возьмите в разделе «Настройки», пункт «Postback URL». Параметры subid и status обязательны: без них трекер постбэк проигнорирует. Параметры lead_status, sale_status и rejected_status учат Keitaro понимать статусы сети.

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

Постбэк уходит методом GET. Кроме {clickid}, {status} и {payout}, LemonAD отдаёт макросы {leadid}, {utmSource}, {utmCampaign}, {utmMedium}, {utmContent}, {utmTerm} и {globalSource}. Если доход нужен только после оплаты лида, используйте {payoutOrZero}: в других статусах он отдаёт 0. Полный список в разделе «Настройка PostBack».
Глобальный постбэк действует на все офферы. Локальный задаётся в карточке одного оффера. По дублям LemonAD по умолчанию постбэки не шлёт: их включают переключателем в том же разделе «Глобальный постбек и API».
Статусы LemonAD и Keitaro
| Статус в LemonAD | Что значит | Имя в кабинете | Конверсия в Keitaro |
|---|---|---|---|
| Новый лид | лид в работе у колл-центра | lead | Lead, доход в холде |
| Подтверждённый | рекламодатель подтвердил заказ | sale | Sale, подтверждённый доход |
| Отклонённый | клиент отказался | rejected | Rejected |
| Треш | невалидные данные или дубль | rejected | Rejected |
| Оплата | лид оплачен | можно не включать | не нужен |
Если треш хочется видеть отдельно, назовите его 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.



