| Загрузка… |
Авторизация для доступа к панели управления
| Загрузка… |
•••). По умолчанию видны все.Постбек — это 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. | |
Текущий статус — lead.status, сменить — setStatus('ключ'). Список ниже тянется из настроек и всегда актуален:
leadСписок полей тянется из «Настройка CRM». Обращение по ключу: lead.ключ; записать — setField('ключ', значение). У переименованного встроенного поля работает и старый ключ (имя колонки).
Всегда доступны (системные, вне настроек): lead.id, lead.stream_id, lead.stream_name, lead.sale_amount. | |
Дата человеком: 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(...).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 }); // кастомное + встроенное поле
}
// 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 });
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 });
Сначала в таблице создайте 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 — это нормально, строка добавляется
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. | |
lead.<ключ>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 });
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 (индексная ротация — быстро даже на сотнях тысяч лидов).