⬡

CRM System

Авторизация для доступа к панели управления

Неверный логин или пароль
⬡
CRMLead Management
Навигация
Лиды
Потоки
Постбеки
Инбек
Лог постбеков
Пользователи
Настройка CRM
API
Telegram
Equilibrium
A
admin

Лиды

Загрузка…

Потоки

Загрузка…

Постбеки

Загрузка…

Инбек — тянет статусы по расписанию

Загрузка…

Лог постбеков

Загрузка…

Пользователи

Загрузка…

Настройка CRM

Загрузка…

API — документация

Загрузка…
Лиды
Потоки
Постбеки
Логи

Лид

Новый поток

Постбеки

Новый пользователь

Передаётся при создании лида в поле user_id — так лид закрепляется за пользователем. Латиница, цифры, . _ -
Пользователь будет видеть лиды только из выбранных потоков.
Потоки не выбраны
Пользователь сможет менять статус у видимых ему лидов (это запускает постбеки). По умолчанию выключено — только просмотр.
Снятые поля пользователь увидит в списке, но их значение будет скрыто (•••). По умолчанию видны все.
Все поля видны

Telegram

Equilibrium — входящие статусы

Новый постбек

Настройки отправки постбека
При срабатывании через поток всегда действуют настройки потока. Эти настройки применяются, когда лиды отправляются напрямую на этот постбек (из «Лиды» → «Отправить на постбек»).
Можно через полночь: напр. 16:00–04:00.

Новый инбек

Привязка и опрос
секунд (мин. 30)
Потоки не выбраны
Можно через полночь: напр. 16:00–04:00.

Документация — серверный постбек (JS)

Постбек — это JS-код, который выполняется на сервере, когда лид получает нужный статус. Отсюда можно отправить данные куда угодно по HTTP: в партнёрки (S2S-постбек), рекламные кабинеты (Facebook CAPI, Google Ads и др.), Google Таблицы, CRM, Telegram-бота, свой API. Песочница, таймаут 15 секунд, без доступа к файловой системе.

Тем же кодом можно обработать ответ и по нему сменить статус лида или записать любые его поля — например, поставить deposit и сохранить payout, если партнёрка ответила «одобрено» (см. раздел «Обработка ответа» ниже).

Функции

await http(url, opts)HTTP-запрос. opts = { method, headers, body } (по умолчанию GET). Возвращает { status, body, headers }. Всегда через await.
log(obj)записать объект в «Лог постбеков» (что отправили / какой ответ).
sha256(str)SHA-256 хеш (нужен для FB CAPI: email/телефон хешируются). Строка нормализуется (trim + lowercase).
console.log(...)тоже попадает в лог отправок.
setStatus(key)сменить статус лида. key — ключ статуса из «Настройка CRM». Неизвестный ключ игнорируется (пишется в лог).
setField(key, value)записать любое поле лида (встроенное или кастомное) по его ключу.
setFields({…})записать несколько полей разом; ключ status внутри тоже сменит статус.
statuses / fieldsмассивы текущих статусов {key,label,color} и полей {key,label,type} из настроек — для проверки/выбора в коде.
Ещё доступны: JSON, Date, Math, Buffer, encodeURIComponent, URLSearchParams, Promise.

Статусы лида — из «Настройка CRM»

Текущий статус — lead.status, сменить — setStatus('ключ'). Список ниже тянется из настроек и всегда актуален:

Поля лида — lead

Список полей тянется из «Настройка CRM». Обращение по ключу: lead.ключ; записать — setField('ключ', значение). У переименованного встроенного поля работает и старый ключ (имя колонки).

Всегда доступны (системные, вне настроек): lead.id, lead.stream_id, lead.stream_name, lead.sale_amount, lead.user_id (номер пользователя), lead.user_code (ID пользователя, как пришёл).
Дата человеком: new Date(lead.created_at * 1000).toISOString()

⚡ Асинхронность и скорость

  • Сетевые вызовы — асинхронные. Всегда пишите await http(...), чтобы дождаться ответа и записать его в лог.
  • Несколько отправок — параллельно через Promise.all (например, в партнёрку и в Google Таблицу одновременно). Это в разы быстрее, чем по очереди:
    // ❌ медленно — по очереди (сумма задержек)
    await http(url1);
    await http(url2);
    
    // ✅ быстро — параллельно (время самого долгого)
    const [r1, r2] = await Promise.all([
      http(url1),
      http(url2),
    ]);
    log({ pp: r1.status, sheet: r2.status });
  • Не блокируйте поток. Никаких тяжёлых циклов/синхронных вычислений — песочница оборвёт скрипт по таймауту, и весь сервер при этом не должен ждать.
  • Ошибка в коде (или таймаут) попадёт в лог как error, отправка не сорвёт обработку других лидов.

Обработка ответа — менять статус и поля лида

  • После await http(...) посмотрите на res.status (HTTP-код) или разберите res.body и вызовите setStatus(...) / setField(...).
  • Работает с вашим конфигом: статус проверяется по списку из «Настройка CRM» (кастомные — тоже), поле пишется по своему ключу — встроенное в свою колонку, кастомное в хранилище кастомных полей.
  • Сумма (sale_amount). Пишется только если статус помечен «принимает сумму» в «Настройка CRM» (по умолчанию — sale). Пример: setStatus('sale'); setField('sale_amount', d.amount); или setFields({ status: 'sale', sale_amount: d.amount }). Число; нечисло/пусто → null. Перевод на статус без флага «принимает сумму» обнуляет sale_amount.
  • Изменения применяются только при реальной отправке (поток / «Отправить сейчас» / очередь). Кнопка «Тест» и «Проверить код» данные лида не трогают — показывают, что было бы применено.
  • Защищены от записи: id, stream_id, created_at, updated_at. Смена статуса здесь не запускает постбеки заново (защита от циклов).
const res = await http('https://partner.example/api/lead', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ phone: lead.phone, click: lead.sub1 }),
});
log({ http: res.status, response: res.body });

// вариант А — по HTTP-коду ответа
if (res.status === 200)      setStatus('valid');
else if (res.status === 409) setStatus('duplicate');
else                         setStatus('invalid');

// вариант Б — по телу ответа + запись полей
let data = {}; try { data = JSON.parse(res.body); } catch {}
if (data.status === 'approved') {
  setStatus('deposit');                 // ваш статус из «Настройка CRM»
  setFields({ payout: data.amount, subid: data.click_id }); // кастомное + встроенное поле
}

Пример 1. S2S-постбек в партнёрку (GET)

// click_id обычно лежит в sub1; payout — из суммы продажи
const url = 'https://partner.example.com/postback?clickid='
  + encodeURIComponent(lead.sub1)
  + '&status=' + lead.status
  + '&payout=' + (lead.sale_amount || 0);
const r = await http(url);
log({ http: r.status, headers: r.headers, response: r.body });

Пример 2. POST JSON в API партнёрки / своей CRM

const r = await http('https://api.example.com/leads', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ВАШ_ТОКЕН' },
  body: JSON.stringify({
    external_id: lead.id,
    name: lead.name, phone: lead.phone, email: lead.email,
    status: lead.status, amount: lead.sale_amount,
    source: lead.utm_source, sub1: lead.sub1,
  }),
});
log({ http: r.status, headers: r.headers, response: r.body });

Пример 3. Запись строки в Google Таблицу

Сначала в таблице создайте Apps Script (Расширения → Apps Script), вставьте код и опубликуйте как веб-приложение (Развернуть → Веб-приложение, доступ «Все»). Полученный URL /exec вставьте в постбек.

// код Apps Script (в самой Google Таблице):
function doPost(e) {
  const d = JSON.parse(e.postData.contents);
  SpreadsheetApp.getActiveSheet().appendRow([
    new Date(), d.id, d.name, d.phone, d.email, d.status, d.amount
  ]);
  return ContentService.createTextOutput('ok');
}
// код постбека:
const r = await http('https://script.google.com/macros/s/ВАШ_ID/exec', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    id: lead.id, name: lead.name, phone: lead.phone,
    email: lead.email, status: lead.status, amount: lead.sale_amount,
  }),
});
log({ http: r.status }); // 302 — это нормально, строка добавляется

Пример 4. Facebook CAPI (событие Lead)

const PIXEL = lead.sub1;            // пиксель из саба лида
const TOKEN = lead.sub2;            // токен из саба лида
const phone = lead.phone ? String(lead.phone).replace(/[^\d+]/g,'') : '';
const event = {
  action_source: 'website',
  event_name: 'Lead',
  event_time: lead.created_at,
  event_id: 'Lead_' + lead.id,
  user_data: {
    client_ip_address: lead.ip || '',
    client_user_agent: lead.ua || '',
    ...(lead.email ? { em: sha256(lead.email) } : {}),
    ...(phone ? { ph: sha256(phone) } : {}),
    external_id: sha256(String(lead.id)),
  },
};
const r = await http(
  'https://graph.facebook.com/v21.0/' + PIXEL + '/events?access_token=' + TOKEN,
  { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ data:[event] }) }
);
log({ http: r.status, headers: r.headers, response: r.body });

Документация — Инбек (опрос статусов)

Инбек — обратный постбек: по расписанию он тянет статусы из партнёрки и по ответу меняет статус/поля лидов. На каждом тике код получает пачку лидов leads[] (из привязанных потоков, со статусами для опроса) и сам решает — опрашивать по одному или одним bulk-запросом. Песочница, без доступа к файловой системе.

Доступно в коде

leadsмассив лидов пачки (обогащённые: lead.<ключ> для кастомных/переименованных полей).
await http(url, opts)HTTP-запрос. opts = { method, headers, body }. Возвращает { status, body, headers }. Всегда через await.
apply(lead, { status, fields })адресно поменять статус и/или поля конкретного лида из пачки.
setStatus(lead, key)сменить статус лида (то же, что apply(lead,{status:key})).
setField(lead, key, value)записать одно поле лида (встроенное или кастомное).
log(obj), console.log(...)запись в лог прогона инбека.
statuses / fieldsтекущие статусы/поля из «Настройка CRM».
Ещё: sha256, JSON, Date, Math, Buffer, encodeURIComponent, URLSearchParams, Promise.

Статусы — из «Настройка CRM»

Поля лида — lead.<ключ>

Пример 1. Bulk-отчёт (1 запрос на всю пачку — Keitaro-стиль)

const r = await http('https://partner.example/api/conversions?from=today');
const rows = JSON.parse(r.body);
const map = {}; for (const row of rows) map[row.click] = row;   // индексируем по click_id
for (const lead of leads) {
  const row = map[lead.sub1];           // click_id обычно в sub1
  if (!row) continue;                    // ещё нет решения — оставляем как есть
  if (row.status === 'sale')   apply(lead, { status: 'deposit', fields: { payout: String(row.amount) } });
  if (row.status === 'reject') apply(lead, { status: 'trash' });
}
log({ processed: leads.length });

Пример 2. По одному клику (партнёрка отдаёт статус на один клик)

for (const lead of leads) {
  const r = await http('https://partner.example/api/status?click=' + encodeURIComponent(lead.sub1));
  const d = JSON.parse(r.body);
  if (d.status === 'approved') apply(lead, { status: 'deposit', fields: { payout: String(d.amount) } });
  else if (d.status === 'rejected') apply(lead, { status: 'invalid' });
  // 'pending' — ничего не делаем, опросим на следующем тике
}
  • Опрашиваются лиды из привязанных потоков со статусами опроса. Как только лид получает статус вне этого набора — он перестаёт опрашиваться.
  • Сумма (sale_amount): пишется только для статусов с флагом «принимает сумму» (по умолчанию sale): apply(lead, { status: 'sale', fields: { sale_amount: row.amount } }). Число; нечисло/пусто → null.
  • «Тест» делает реальные запросы, но не пишет в лиды — показывает, что изменилось бы. «Запустить сейчас» и планировщик — применяют.
  • Защищены от записи: id, stream_id, created_at, updated_at. Смена статуса инбеком не запускает постбеки повторно.
  • Размер пачки ограничивает число лидов за тик; лиды обходятся по кругу по id (индексная ротация — быстро даже на сотнях тысяч лидов).

Выбор потоков