Интеграция KMA.biz с Keitaro: API и постбэк
Интеграция KMA.biz (KissMyAds) с Keitaro: API-ключ и код потока, рабочий api.php для api.kma.biz, глобальный постбэк и расшифровка статусов A, P, D, F…

Интеграция KMA.biz с Keitaro строится так: форма на лендинге отправляет заявку на https://api.kma.biz/lead/add с ключом в заголовке Authorization и ID клика трекера в поле data1. KMA возвращает этот ID в глобальном постбэке макросом {data1} вместе со статусом A, P, D или F, и Keitaro записывает конверсию.
Ниже рабочий api.php, форма, постбэк, таблица статусов и разбор ошибок. Документация API у KMA закрыта в кабинете, поэтому для каждого параметра указано, откуда он взят, а адрес API мы проверили запросами 1 октября 2026 года.
- KMA в 2026 году: kma.biz открывает kissmyads.pro
- Как работает связка KMA и Keitaro
- Где взять API-ключ и код потока
- Параметры API KMA
- Форма на лендинге
- PHP-обработчик api.php для KMA
- Как проверить отправку
- Постбэк KMA в Keitaro
- Шаг 1. Партнёрская сеть в Keitaro
- Шаг 2. Глобальный Postback в кабинете KMA
- Статусы KMA и Keitaro
- Частые ошибки
- Частые вопросы
- Что случилось с kma.biz?
- Где взять API-ключ KMA?
- Какой адрес API у KMA?
- В какое поле KMA передавать subid Keitaro?
- Что значат статусы A, P, D, F в KMA?
KMA в 2026 году: kma.biz открывает kissmyads.pro
- Сеть работает под именем KissMyAds. Адрес kma.biz перенаправляет на kissmyads.pro, туда же ведёт старый cabinet.kma.biz. На главной: нутра с инхаус-офферами, больше 1900 офферов и 104 гео, на рынке с 2013 года, гемблинга нет.
- Вход и регистрация теперь на kissmyads.pro. Раздел ключа API по старому пути
/user/settings/api-keyна новом домене открывает форму входа, то есть он на месте. - API прежнее.
api.kma.bizотвечает: без ключа{"code":6,"message":"Invalid auth data!"}, с неверным ключом{"code":3,"message":"Access token incorrect!"}.
Наш опыт 2024 года: KMA удобна по выводу денег. Достаточно нажать две кнопки в кабинете, и к 19:00 выплата приходила на кошелёк. Инхаус-офферы обычно шли с гарантом, а вот ставки иногда приходилось выбивать. Что сейчас пишут об условиях выплат на сайте и в оферте, мы сравнили в подборке CPA-сетей для новичков. Офферы сети собраны в каталоге офферов KMA.
Как работает связка KMA и Keitaro
- Клик приходит в Keitaro, трекер создаёт subid и показывает лендинг.
- Keitaro подставляет subid в форму вместо
{subid}. api.phpотправляет лид в KMA с кодом потока вchannelи subid вdata1.- KMA шлёт постбэк при создании заказа, апруве, отмене и фейке. Keitaro по
{data1}находит клик и записывает конверсию.
Справка Keitaro для лендингов KMA тоже передаёт ID клика в data1: в форму добавляют <input type="hidden" name="data1" value="{subid}">. Это способ для лендинга, скачанного из KMA вместе с её скриптами. Для своего лендинга нужен свой обработчик, его код ниже.
Где взять API-ключ и код потока
- Доступ к API. В инструкции KMA к API-пакету сказано, что доступ к добавлению лидов по API нужно запросить у персонального менеджера. Сделайте это заранее.
- API-ключ. В кабинете на странице
/user/settings/api-key. Раньше она открывалась на cabinet.kma.biz, сейчас на kissmyads.pro после входа. - Код потока. Откройте «Офферы», «Все офферы», нажмите «Создать поток» у нужного оффера. Код потоковой ссылки и есть
channel. - Список полей в кабинете: «Инструменты», «API», вкладка «Методы создания лидов». Там же лежит готовый PHP-пакет KMA.
Параметры API KMA
Лид отправляется методом POST обычной формой (application/x-www-form-urlencoded). Ключ передаётся в заголовке Authorization: Bearer API_KEY. Это подтверждают инструкции WebJack и наш запрос к API.
| Поле | Обязательно | Что передавать |
|---|---|---|
channel | да | код потока |
name, phone | да | имя и телефон |
ip | да | IP клиента, а не вашего сервера |
country | да | код страны из двух букв |
referer | да | откуда клиент пришёл на лендинг, а не адрес вашего сайта |
data1 | для постбэка | subid из Keitaro, вернётся макросом {data1} |
data2…data5 | нет | свои метки: источник, кампания, объявление |
ua | нет | user agent клиента, поле из нашего прошлого скрипта |
Обязательные поля и заголовки перечислены по странице генератора обработчика для KMA в блоге info-biznez.ru: официальный список доступен только в кабинете. В PHP-пакете самой KMA поля data1…data5 заняты utm-метками, поэтому при работе с Keitaro data1 отдают под subid, а метки сдвигают. Успешным считается ответ с "code":0, по данным WebJack в нём приходит номер заказа. Любой другой code означает ошибку, текст лежит в поле message.
Форма на лендинге
Главный файл лендинга назовите 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}">
<!-- уйдут в data2-data5 -->
<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}">
<input type="hidden" name="utm_term" value="{utm_term}">
<button type="submit">Заказать</button>
</form>Поле referrer с макросом {referrer} отдаёт KMA реальный источник перехода, например Facebook. Без него скрипт возьмёт адрес страницы с формой. Если лендинг лежит на вашем хостинге, допишите к его ссылке в трекере ?subid={subid}, а subid в форме выводите так:
<!-- ссылка на лендинг в Keitaro: https://ВАШ_ЛЕНДИНГ/?subid={subid} -->
<input type="hidden" name="subid" value="<?= htmlspecialchars($_GET['subid'] ?? '', ENT_QUOTES) ?>">PHP-обработчик api.php для KMA
Заполните API_KEY, CHANNEL и COUNTRY. Нужен PHP 7.2 или новее с curl и mbstring.
<?php
// api.php: принимает заявку с формы лендинга и отправляет лид в KMA (KissMyAds) по API
declare(strict_types=1);
// ===== Настройки =====
const API_URL = 'https://api.kma.biz/lead/add';
const API_KEY = 'API_KEY'; // кабинет KMA: /user/settings/api-key (доступ к API включает менеджер)
const CHANNEL = 'CHANNEL'; // код потоковой ссылки (потока)
const COUNTRY = 'BD'; // код страны оффера, две буквы
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 = [
'channel' => CHANNEL,
'name' => $name,
'phone' => $phone,
'ip' => client_ip(),
'country' => COUNTRY,
'referer' => referer_url(),
'ua' => user_agent(),
'data1' => $subid, // вернётся в постбэке макросом {data1}
'data2' => post_macro('utm_source'),
'data3' => post_macro('utm_campaign'),
'data4' => post_macro('utm_content'),
'data5' => post_macro('utm_term'),
];
$res = http_post(API_URL, http_build_query(compact_fields($lead)), [
'Content-Type: application/x-www-form-urlencoded',
'Authorization: Bearer ' . API_KEY,
]);
$answer = json_decode($res['body'], true);
$ok = is_array($answer) && isset($answer['code']) && (int) $answer['code'] === 0;
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 года:
- Включена проверка SSL. В старом коде стояло
CURLOPT_SSL_VERIFYPEER = false: так ключ и данные клиентов можно перехватить при подмене сертификата. - Посетитель не видит технических ошибок. Раньше при сбое на экран выводился текст ошибки API, теперь посетитель попадает на
error.php, а ответ KMA пишется вdata/leads-ГГГГ-ММ-ДД.log. - Реальный IP за Cloudflare. Старый скрипт брал REMOTE_ADDR, за Cloudflare это адрес узла Cloudflare. KMA просит IP клиента.
- Телефоны не лежат открытым текстом. Вместо
leads.txtскрипт хранит HMAC-хеши номеров 24 часа, чтобы не слать повторы. - Таймаут 3 секунды. Столько Keitaro даёт PHP локального лендинга.
Код проверен против локальной заглушки API в восьми сценариях: успех, повтор номера, ошибка, HTML 500, ответ дольше 3 секунд, пустой телефон, GET и форма без subid. Против настоящего api.kma.biz с неверным ключом скрипт получил "code":3 и отправил посетителя на страницу ошибки. Готовые страницы для редиректа: «Спасибо за заказ» и страница ошибки.
Как проверить отправку
- Загрузите архив с
index.php,api.php,success.phpиerror.phpв Keitaro как локальный оффер, создайте тестовую кампанию с одним потоком. - Откройте ссылку кампании и найдите в коде страницы поле
subid: там должен быть ID клика. - Отправьте форму. В журнале в папке
dataнужна строка с"result":"sent"и"code":0в ответе. - Найдите заказ в статистике KMA и проверьте, что в data1 записан ID клика.
Постбэк KMA в Keitaro
Шаг 1. Партнёрская сеть в Keitaro
В Keitaro откройте «Партнерские сети», нажмите «Создать» и выберите шаблон Kma.biz. В заметке к шаблону написано: в кабинете KMA зайдите в «Инструменты», «Глобальный Postback» и вставьте Postback URL в поле Url.

Минимальная ссылка постбэка с обучением статусов:
https://ВАШ_ДОМЕН/POSTBACK_KEY/postback?subid={data1}&payout={sum}&status={status}¤cy={currency}&lead_status=P&sale_status=A&rejected_status=D,FВАШ_ДОМЕН и POSTBACK_KEY возьмите в Keitaro: «Настройки», «Postback URL». Шаблон Keitaro дописывает ещё stream, orderid, bannerid и campaignid, на запись конверсии они не влияют.
Шаг 2. Глобальный Postback в кабинете KMA
- Откройте «Инструменты», «Глобальный Postback».
- Вставьте ссылку из Keitaro в поле Url.
- Метод передачи: GET.
- Отметьте все статусы: «Отклонение», «Апрув», «Конверсия», «Фейк».
- Галочку «Отправлять комиссию только со статусом Апрув» не ставьте, иначе выплата в Keitaro будет приходить только по апрувам.
- Нажмите «Применить». Кнопкой «Postback тест» можно проверить, что трекер отвечает, но конверсию тест не создаст.

Глобальный постбэк работает для всех офферов. В настройках потока можно отметить «Использовать глобальный postback» или задать отдельный. Макросы KMA по шаблонам трекеров AnyTrack, AdsBridge и Kadam: {data1}…{data5}, {sum} (выплата), {status}, {currency}, {stream}, {orderid}, {bannerid}, {campaignid}, {ip}, {date}, {countrycode}.
Статусы KMA и Keitaro
KMA отдаёт статус одной буквой. Расшифровка есть в подсказке рядом с полем постбэка в кабинете KMA (скриншот выше) и в справке AlterCPA.
| Статус KMA | Что значит | Обучение в ссылке | Конверсия в Keitaro |
|---|---|---|---|
P | pending, новый заказ | lead_status=P | Lead, доход в холде |
A | approved, апрув | sale_status=A | Sale |
D | decline, отмена | rejected_status=D,F | Rejected |
F | fake, фейк | rejected_status=D,F | Rejected |
Частые ошибки
- «Access token incorrect!» (code 3). Неверный или старый ключ. Возьмите его заново на странице ключа API в кабинете.
- «Invalid auth data!» (code 6). Заголовок Authorization не дошёл: проверьте, что он записан как
Bearerи пробел перед ключом. - Ключ верный, а лиды не принимаются. Аккаунту не открыт доступ к добавлению лидов по API. Напишите менеджеру.
- В referer адрес вашего сайта. KMA ждёт источник перехода клиента. Передайте
{referrer}из Keitaro, как в форме выше. - В Keitaro «Click for subid REPLACE not found». В
data1не ушёл ID клика: в форме нет поля с{subid}или лендинг открыт не через трекер. - «Conversion type not found». В ссылке нет обучения
lead_status=P&sale_status=A&rejected_status=D,F, и трекер не понимает буквы. Разбор остальных ошибок в справке Keitaro.
Бонус из прошлой редакции: архив лендинга Nuts and Herbs BD на потенцию для Бангладеш, который мы долго лили на KMA, и он хорошо конвертил. В архиве только HTML: добавьте форму и обработчик из статьи. Доступен ли оффер сейчас, уточните у менеджера.
Частые вопросы
Что случилось с kma.biz?
Сеть работает, адрес kma.biz перенаправляет на kissmyads.pro. API по-прежнему на api.kma.biz.
Где взять API-ключ KMA?
В кабинете на странице /user/settings/api-key. Доступ к созданию лидов по API дополнительно включает персональный менеджер.
Какой адрес API у KMA?
https://api.kma.biz/lead/add, метод POST, данные формой, ключ в заголовке Authorization: Bearer. Успешный ответ содержит "code":0.
В какое поле KMA передавать subid Keitaro?
В data1. Его возвращает макрос {data1} в постбэке, так же делают справка Keitaro и шаблоны других трекеров.
Что значат статусы A, P, D, F в KMA?
A апрув, P новый заказ в ожидании, D отмена, F фейк. В Keitaro их сопоставляют параметрами lead_status, sale_status и rejected_status.



