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

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

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

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

Интеграция 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

  • Сеть работает под именем 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

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

Справка Keitaro для лендингов KMA тоже передаёт ID клика в data1: в форму добавляют <input type="hidden" name="data1" value="{subid}">. Это способ для лендинга, скачанного из KMA вместе с её скриптами. Для своего лендинга нужен свой обработчик, его код ниже.

Где взять API-ключ и код потока

  1. Доступ к API. В инструкции KMA к API-пакету сказано, что доступ к добавлению лидов по API нужно запросить у персонального менеджера. Сделайте это заранее.
  2. API-ключ. В кабинете на странице /user/settings/api-key. Раньше она открывалась на cabinet.kma.biz, сейчас на kissmyads.pro после входа.
  3. Код потока. Откройте «Офферы», «Все офферы», нажмите «Создать поток» у нужного оффера. Код потоковой ссылки и есть channel.
  4. Список полей в кабинете: «Инструменты», «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 и отправил посетителя на страницу ошибки. Готовые страницы для редиректа: «Спасибо за заказ» и страница ошибки.

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

  1. Загрузите архив с index.php, api.php, success.php и error.php в Keitaro как локальный оффер, создайте тестовую кампанию с одним потоком.
  2. Откройте ссылку кампании и найдите в коде страницы поле subid: там должен быть ID клика.
  3. Отправьте форму. В журнале в папке data нужна строка с "result":"sent" и "code":0 в ответе.
  4. Найдите заказ в статистике KMA и проверьте, что в data1 записан ID клика.

Постбэк KMA в Keitaro

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

В Keitaro откройте «Партнерские сети», нажмите «Создать» и выберите шаблон Kma.biz. В заметке к шаблону написано: в кабинете KMA зайдите в «Инструменты», «Глобальный Postback» и вставьте Postback URL в поле Url.

Настройка постбеков кейтаро для ПП kma.biz

Минимальная ссылка постбэка с обучением статусов:

https://ВАШ_ДОМЕН/POSTBACK_KEY/postback?subid={data1}&payout={sum}&status={status}&currency={currency}&lead_status=P&sale_status=A&rejected_status=D,F

ВАШ_ДОМЕН и POSTBACK_KEY возьмите в Keitaro: «Настройки», «Postback URL». Шаблон Keitaro дописывает ещё stream, orderid, bannerid и campaignid, на запись конверсии они не влияют.

Шаг 2. Глобальный Postback в кабинете KMA

  1. Откройте «Инструменты», «Глобальный Postback».
  2. Вставьте ссылку из Keitaro в поле Url.
  3. Метод передачи: GET.
  4. Отметьте все статусы: «Отклонение», «Апрув», «Конверсия», «Фейк».
  5. Галочку «Отправлять комиссию только со статусом Апрув» не ставьте, иначе выплата в Keitaro будет приходить только по апрувам.
  6. Нажмите «Применить». Кнопкой «Postback тест» можно проверить, что трекер отвечает, но конверсию тест не создаст.
Настройка постбеков для ПП kma.biz

Глобальный постбэк работает для всех офферов. В настройках потока можно отметить «Использовать глобальный postback» или задать отдельный. Макросы KMA по шаблонам трекеров AnyTrack, AdsBridge и Kadam: {data1}…{data5}, {sum} (выплата), {status}, {currency}, {stream}, {orderid}, {bannerid}, {campaignid}, {ip}, {date}, {countrycode}.

Статусы KMA и Keitaro

KMA отдаёт статус одной буквой. Расшифровка есть в подсказке рядом с полем постбэка в кабинете KMA (скриншот выше) и в справке AlterCPA.

Статус KMAЧто значитОбучение в ссылкеКонверсия в Keitaro
Ppending, новый заказlead_status=PLead, доход в холде
Aapproved, апрувsale_status=ASale
Ddecline, отменаrejected_status=D,FRejected
Ffake, фейкrejected_status=D,FRejected
Таблица прокручивается вбок

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

  • «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.

Редакция BoostClicks

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