nutrastar.media

Урок 1.1. Ручная настройка трекера без ИИ

То же самое, что в первом уроке, но руками: команды, конфиги, скрипт на лендинг, колонки и схема статусов — без агентов и автоматики.

Курс создан совместно с партнёрской сетью

Это приложение к первому уроку. Там всё ставит ИИ-агент по одному промту, а здесь то же самое руками: какие команды выполнять, какой скрипт и куда вставлять на лендинге, где прописывается ключ партнёрки, как завести колонки в трекере.

Порядок важен: сначала сервер и домены, потом лендинги, потом постбеки, в конце колонки.


1. Установка трекера

Сервер: Ubuntu 24.04 или 26.04, минимум 2 ядра, 4 ГБ памяти, 50 ГБ диска. Сервер должен быть чистым — без nginx, php и mysql, иначе установщик подерётся за порты.

wget https://data.binom.org/binom_v2_install.sh -O /root/binom_v2_install.sh
bash /root/binom_v2_install.sh install --email=ВАШ_EMAIL

Частая засада. На свежем VPS в фоне работает автообновление и держит блокировку apt. Установка падает с ошибкой про Could not get lock. Лечится так:

ps aux | grep apt              # смотрим, что висит
systemctl stop unattended-upgrades
dpkg --configure -a
# и запускаем установку заново

Ставится минут десять, поднимается девять контейнеров. После установки доступы лежат в файле /root/binom/.binom — заберите их сразу, файл самоудаляется через 2 часа.


2. Домены

Все A-записи ведут прямо на IP сервера. Если домены на Cloudflare — статус записи «DNS only», серое облако. С оранжевым сертификат трекер обычно всё равно получит, но прокси встанет между вами и кликами: постбеки от партнёрки могут не дойти, вместо редиректа посетитель иногда увидит проверочную страницу, а IP в отчётах будет не его. Поэтому серое.

dig +short A ваш-домен     # должен вернуть IP сервера, а не адрес Cloudflare

Домен 1 — админка

docker exec binom_backend php bin/console app:managed:domain:init --host=ДОМЕН_АДМИНКИ

После этого вход в панель — по адресу https://ДОМЕН_АДМИНКИ/index. Сертификат выпустится сам при первом заходе.

Домен 2 — ссылки кампаний

Добавляется в самом трекере: Domains справа сверху, вставляете домен, сохраняете. Проверка: откройте https://ДОМЕН_КАМПАНИЙ/click — в ответ придёт Bad Request. Это правильно, значит эндпоинт живой.

Домен 3 — лендинги

Трекер не хостит произвольные PHP-лендинги, поэтому поднимаем рядом лёгкий хостинг:

mkdir -p /var/www/landers
docker run -d --name landers-php --restart unless-stopped \
  -p 127.0.0.1:8090:80 -v /var/www/landers:/var/www/html php:8.2-apache

Дальше создаём маршрут для домена. Файл /var/lib/docker/volumes/binom_proxy_config/_data/domains/ДОМЕН_ЛЕНДОВ.caddy:

http://ДОМЕН_ЛЕНДОВ,
https://ДОМЕН_ЛЕНДОВ {
        rewrite * /ДОМЕН_ЛЕНДОВ{path}
        reverse_proxy 127.0.0.1:8090
        log {
                level ERROR
                output file /var/log/ДОМЕН_ЛЕНДОВ.error.log
        }
}
docker exec binom_proxy caddy validate --config /etc/caddy/Caddyfile
docker exec binom_proxy caddy reload --config /etc/caddy/Caddyfile

Файлы лендингов кладём в /var/www/landers/ДОМЕН_ЛЕНДОВ/путь/, открываются они как https://ДОМЕН_ЛЕНДОВ/путь/. После каждой заливки:

chown -R www-data:www-data /var/www/landers

И обязательно выключите показ ошибок PHP, чтобы пользователь не видел пути и куски кода: добавьте display_errors = Off в конфиг php внутри контейнера landers-php.


3. Скрипт на лендинг

Этот скрипт собирает вовлечённость и ботов — те самые колонки, по которым мы чистим трафик в четвёртом уроке. Вставляется перед закрывающим тегом </body> на каждом лендинге.

Что поменять: ВАШ-ДОМЕН-ТРЕКЕРА на домен кампаний, ВАШ-ДОМЕН-ЛЕНДОВ на домен, где лежат лендинги.

<script>
(function () {
  var TRACKER_BASE = 'https://ВАШ-ДОМЕН-ТРЕКЕРА/click';

  var qs = new URLSearchParams(window.location.search);
  var clickid = qs.get('subacc') || qs.get('click_id') || qs.get('clickid') || '';

  // заполнить скрытое поле формы click_id (если есть) значением из URL
  var _ci = document.getElementById('click_id');
  if (_ci && clickid) _ci.value = clickid;

  var fired = { time15: false, time30: false, scroll25: false, scroll50: false, scroll75: false, scroll90: false };

  function fireEvent(num, value) {
    if (!clickid) return;
    var url = TRACKER_BASE + '?upd_clickid=' + encodeURIComponent(clickid) + '&event' + num + '=' + value;
    fetch(url, { method: 'GET', mode: 'no-cors', cache: 'no-cache' }).catch(function () {});
  }

  setTimeout(function () { if (!fired.time15) { fired.time15 = true; fireEvent(6, 1); } }, 15000);
  setTimeout(function () { if (!fired.time30) { fired.time30 = true; fireEvent(7, 1); } }, 30000);

  window.addEventListener('scroll', function () {
    var top = window.scrollY || document.documentElement.scrollTop;
    var full = document.documentElement.scrollHeight - window.innerHeight;
    if (full <= 0) return;
    var pct = Math.round((top / full) * 100);
    [25, 50, 75, 90].forEach(function (t) {
      var key = 'scroll' + t;
      if (pct >= t && !fired[key]) { fired[key] = true; fireEvent(8, t); }
    });
  }, { passive: true });

  // Binom засчитывает LP click на первом запросе; дальше редирект на заглушку оффера, его отказ игнорируем
  function fireLpClick() {
    if (!clickid || fired.lp) return;
    fired.lp = true;
    fetch(TRACKER_BASE + '?lp=1&bcid=' + encodeURIComponent(clickid),
      { method: 'GET', mode: 'no-cors', keepalive: true }).catch(function () {});
  }

  var ORDER_ZONE = '#promo-main-wrapper-aptboxes,[class*="wheel"],[class*="door"],[class*="gift"],[class*="prize"],[class*="spin"],[class*="roulette"],[class*="scratch"]';

  function inOrderZone(el) {
    if (!el || !el.closest) return false;
    if (el.closest(ORDER_ZONE)) return true;
    var form = el.closest('form');
    return !!(form && form.querySelector('[name="phone"]'));
  }

  document.addEventListener('click', function (e) {
    if (inOrderZone(e.target)) fireLpClick();
    var img = e.target.closest && e.target.closest('img');
    if (img && img.naturalWidth > 1 && !fired.img) { fired.img = true; fireEvent(9, 1); }
  }, true);

  document.addEventListener('focusin', function (e) {
    if (inOrderZone(e.target)) fireLpClick();
  }, true);

  // event10 — простой чекер: на мобильном трафике заход без touch = подозрительно (десктоп/эмулятор)
  var _isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0);
  if (!_isTouch) fireEvent(10, 1);

  // event11 — BotD (headless/Selenium/Puppeteer/Playwright), самохост, телеметрия выключена
  if (clickid) {
    try {
      var _b = document.createElement('script');
      _b.src = 'https://ВАШ-ДОМЕН-ЛЕНДОВ/botd.min.js';
      _b.async = true;
      _b.onload = function () {
        if (window.BotD && window.BotD.load) {
          window.BotD.load({ monitoring: false })
            .then(function (a) { return a.detect(); })
            .then(function (r) { if (r && r.bot) fireEvent(11, 1); })
            .catch(function () {});
        }
      };
      document.head.appendChild(_b);
    } catch (e) {}
  }
})();
</script>

Что делает каждый кусок

  • event6 и event7 — посетитель пробыл на странице 15 и 30 секунд.
  • event8 — глубина скролла, отсечки 25, 50, 75 и 90 процентов.
  • event9 — клик по картинке, то есть живое взаимодействие с контентом.
  • event10 — у устройства нет тача. На мобильном трафике это повод присмотреться, но сам по себе не бот: так же выглядят десктоп и некоторые браузеры.
  • event11 — визит, который библиотека BotD пометила как подозрительный по отпечатку браузера. Это оценка, а не приговор.
  • lp=1 — клик в зону заказа: форма, колесо, двери, коробки. Это LP click в отчёте.

Важно про номера событий. События с 1 по 5 и с 25 по 30 не трогайте: 25–30 заняты схемой статусов, о ней ниже.

BotD кладём на свой домен

Библиотека бесплатная, 12 КБ. Скачиваем и кладём в корень домена лендингов, чтобы не ходить на чужой CDN:

curl -sL https://openfpcdn.io/botd/v2/umd.min.js \
  -o /var/www/landers/ДОМЕН_ЛЕНДОВ/botd.min.js

Если на лендинге есть форма

Добавьте в форму скрытое поле — скрипт сам подставит в него click id из ссылки:

<input type="hidden" name="subacc" id="click_id" value="">

4. Ключ партнёрки и отправка заявок

Тут два случая. Если вы льёте на готовый пакет лендинга из AdCombo — в нём уже есть своя отправка, нужно только добавить отметку лида в трекер. Если лендинг свой, с формой — отправку в партнёрку пишем сами.

Где взять ключ

API-ключ лежит в кабинете AdCombo в настройках профиля. Он один на аккаунт и нужен для отправки заявок с вашего лендинга.

Свой лендинг: конфиг

Файл api/config.php рядом с лендингом:

<?php
$apiKey = 'ВАШ_API_КЛЮЧ';
$apiCreateOrderUrl = 'https://api.adcombo.com/api/v2/order/create/';
$offer_id = 40792;        // ID оффера из партнёрки
$country_code = 'CD';     // страна оффера
$price = 50;              // цена оффера
$binomClickUrl = 'https://ВАШ-ДОМЕН-ТРЕКЕРА/click';

Свой лендинг: отправка заявки

Ключевые места файла api/send_lead.php:

// 1. click id — из формы, а если его там нет, из адреса страницы
$click_id = @$_POST['click_id'] ?: @$_POST['clickid'] ?: @$_POST['subacc'];

// 2. IP посетителя — ОБЯЗАТЕЛЬНО из заголовков
$ip = '';
foreach (array('HTTP_CF_CONNECTING_IP','HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP') as $k) {
    if (!empty($_SERVER[$k])) { $ip = trim(explode(',', $_SERVER[$k])[0]); break; }
}
if (!$ip) $ip = $_SERVER['REMOTE_ADDR'];

// 3. отправка в партнёрку
$params = array('api_key'=>$apiKey, 'name'=>@$_POST['name'], 'phone'=>@$_POST['phone'],
                'offer_id'=>$offer_id, 'country_code'=>$country_code, 'price'=>$price,
                'ip'=>$ip, 'subacc'=>$click_id);
$res = @file_get_contents($apiCreateOrderUrl . '?' . http_build_query($params));

// 4. отметка лида в трекере
if ($click_id) {
    @file_get_contents($binomClickUrl . '?' . http_build_query(
        array('cnv_id'=>$click_id, 'payout'=>0, 'cnv_status'=>'lead')));
}

Про IP — это не мелочь. Лендинги стоят за прокси, и в REMOTE_ADDR приезжает внутренний адрес вроде 172.17.0.1. Партнёрка на такой отвечает ошибкой Wrong IP и заявка не создаётся вообще. Именно поэтому IP берётся из заголовков.

Готовый пакет из партнёрки

В пакетах AdCombo отправка уже написана и IP берётся правильно. Нужно только добавить отметку лида в трекер. В файле order.php находим строку, где партнёрка ответила успехом, и сразу после неё вставляем:

if ($data['code'] == 'ok') {
    // click id: из запроса, а если его нет — из адреса страницы, с которой пришли
    $binom_click_id = '';
    foreach (array('subacc','click_id','clickid') as $key) {
        if (!empty($_REQUEST[$key])) { $binom_click_id = $_REQUEST[$key]; break; }
    }
    if (!$binom_click_id && isset($_SERVER['HTTP_REFERER'])) {
        $ref = parse_url($_SERVER['HTTP_REFERER']);
        if (isset($ref['query'])) {
            parse_str($ref['query'], $rp);
            foreach (array('subacc','click_id','clickid') as $key) {
                if (!empty($rp[$key])) { $binom_click_id = $rp[$key]; break; }
            }
        }
    }
    if ($binom_click_id) {
        @file_get_contents('https://ВАШ-ДОМЕН-ТРЕКЕРА/click?' . http_build_query(array(
            'cnv_id' => $binom_click_id, 'payout' => 0, 'cnv_status' => 'lead')));
    }

5. Постбеки

Подробно кто кому что шлёт — во втором уроке, здесь коротко, что куда вставить.

Партнёрка → трекер

В кабинете AdCombo, во все три поля постбека:

https://ВАШ-ДОМЕН-ТРЕКЕРА/click?cnv_id={subacc}&payout={revenue}&cnv_status={status}

Здесь {subacc}, {revenue} и {status} — макросы партнёрки. {status} обязательно оставить макросом, иначе развалится разбивка по статусам.

Трекер → сетка

В карточке источника трафика. Одной ссылки здесь мало: заявка и подтверждённый заказ — разные события, и сетке их надо отдавать по отдельности. Иначе она запомнит полную выплату уже на холде, а про отказ не узнает никогда.

Поле Postback URL — ссылка лидовой цели, с нулём вместо выплаты:

MGID:      https://a.mgid.com/postback/ВАШ_CLIENT_ID?c={externalid}&e=Lead&r=0
Adskeeper: https://a.adskeeper.com/postback/ВАШ_CLIENT_ID?c={externalid}&e=new&r=0

Режим S2S — Send all. При «Send only first» апрув не уйдёт: первое событие уже потрачено на холде.

Галка Status-postback relation — и три пары «статус → ссылка»:

hold       →  https://a.mgid.com/postback/ВАШ_CLIENT_ID?c={externalid}&e=Lead&r=0
lead       →  https://a.mgid.com/postback/ВАШ_CLIENT_ID?c={externalid}&e=Lead&r=0
confirmed  →  https://a.mgid.com/postback/ВАШ_CLIENT_ID?c={externalid}&e=Approve&r={payout}
Как это выглядит в карточке источника
Как это выглядит в карточке источника

Выплату несёт только цель апрува. {externalid} без подчёркивания, в {payout} не потеряйте закрывающую скобку, имя в e= должно совпадать с целью в кабинете сетки. Как завести вторую цель — во втором уроке.


6. Колонки в трекере

Создавайте колонки через интерфейс: Settings → Stats → Columns → Add custom column. Тогда все внутренние связи создадутся сами.

Leads (общие)   — встроенная, просто включите в списке колонок
H. Leads        event_25
A. Leads        event_27
R. Leads        event_29
Time 15s, %     event_6 / clicks * 100
Time 30s, %     event_7 / clicks * 100
Scroll %        event_8 / clicks
No-touch %      event_10 / clicks * 100
Bots %          event_11 / clicks * 100
Approve %       event_27 / (event_25 + event_27 + event_29) * 100

Ловушка с процентами. Формат «%» в трекере только дописывает знак процента, он не умножает на сто. Поэтому умножение прописано прямо в формуле.

Если создаёте колонки напрямую в базе — учтите, что колонка живёт в двух таблицах: в одной формула, в другой список страниц, где её показывать. Вставите только в первую — колонка не появится, а редактор выдаст ошибку ViewOnPage not found. Через интерфейс этой проблемы нет.


7. Схема статусов

Это то, что превращает постбеки партнёрки в колонки H/A/R Leads. Настраивается в Settings → Postback processing → схема E-commerce, события с 25 по 30.

  • APPLY FOR: All campaigns. Без этого схема просто не работает.
  • Блок lead/hold — впишите статусы, которыми партнёрка сообщает о новой заявке. Действие: +1 к event_25.
  • Условие на блок lead/hold: предыдущий статус конверсии пустой. Без этого условия каждый повторный постбек снова добавляет холд-лид, и цифра задваивается.
  • Блок confirmed (если предыдущий статус был lead или hold) — минус event_25, плюс event_27, плюс выплата.
  • Блоки cancelled и trash — откат: плюс event_29.
  • Удалите во всех блоках действие Set 0 to conversion — тогда встроенная колонка Leads покажет все лиды, а не только апрувы. Действие Set 0 to payout оставьте: доход должен считаться только по подтверждённым.

Если правите схему не через интерфейс, а напрямую в базе — после правок перезапустите контейнер binom_traffic_distribution, он кэширует правила. При сохранении через интерфейс перезапуск не нужен.


8. Проверка перед запуском

  1. Все девять контейнеров трекера в статусе Up.
  2. Панель открывается по адресу админки, сертификат валиден.
  3. Адрес /click на домене кампаний отвечает Bad Request на пустой запрос.
  4. Тестовая страница на домене лендингов открывается по https.
  5. Открыли лендинг со ссылкой с click id, подождали 15 секунд, полистали — в отчёте появились события.
  6. Оставили тестовую заявку: в партнёрке появился заказ, в трекере строка со статусом lead.
  7. Через несколько минут от партнёрки пришёл статус hold — значит постбек работает.

Если что-то не заводится, не сидите сутки: у трекера есть поддержка, а у партнёрки — ваш менеджер. Обычно проблема в одном символе: перепутанный макрос, лишняя оранжевая тучка в Cloudflare или забытый chown.

Бесплатно, от партнёра курса

Чат с обучением по нативкам, Facebook и TikTok

AdCombo ведёт бесплатное обучение по нативным сетям, Facebook и TikTok. Доступ в чат открывается через бота — платить и покупать ничего не нужно.

@combolearn_bot
Основное пишу в Telegram. Что льётся, где косячат сетки, что сожрало бюджет — каждый день, без редактуры.
@nutrastar
Дальше

Читать следующим

01 Урок 0. Как устроен залив и старт без бюджета Как устроена нутра изнутри: воронка от тизера до выплаты, словарь терминов и первая связка на бесплатном трекере BeMob без вложений в софт. Обучение × AdCombo17 мин чтения2026-09-30 02 Урок 1. Свой сервер и трекер Binom Домены, сервер и установка Binom v2: как собрать нормальный сетап, прошить лендинги событиями и связать трекер с партнёркой и сеткой. Обучение × AdCombo22 мин чтения2026-09-25 03 Урок 2. Загрузка кампании в MGID и Adskeeper Разбор каждого поля в кабинете тизерки: таргетинг, ставки, модерация и две цели конверсии, чтобы сетка видела заявки и деньги отдельно. Обучение × AdCombo8 мин чтения2026-09-28 04 Урок 3. Математика связки: когда выключать площадку EPL, порог в кликах, break-even CR и ROI на пальцах. Сколько можно потратить на площадку, прежде чем выключать её без сожалений. Обучение × AdCombo5 мин чтения2026-09-30
Генеральныеспонсоры
Весь мой стек →