Каждый раз когда новый клиент подключает наш postback-API, в чате повторяется один и тот же диалог: «А что такое click_id?», «Где взять macros?», «Чем s2s отличается от обычного callback?». Терминов в этой теме штук десять, но они кочуют между документациями разных трекеров с разными названиями, и собрать всё в одной голове трудно.
Этот текст — короткий справочник. Без длинных рассуждений: каждый термин — определение, зачем нужен, пример. В конце — рабочий curl-сценарий и чек-лист дебага, когда postback не доходит.
Click — момент перехода по ссылке. Conversion — целевое действие (заказ, лид). Postback = S2S callback = server-to-server HTTP-запрос от вашего сервера к нашему, чтобы сшить click с conversion. Ключ связи — click_id. Macros — токены вида {click_id} в шаблоне postback-URL, которые мы заменяем на реальные значения при срабатывании. Hash — подпись для проверки, что postback пришёл от вас, а не от случайного скрипта.
Click
Момент, когда пользователь кликнул по вашей короткой ссылке (tds.so/abc или go.brand.com/abc). В этот момент мы:
- генерируем уникальный
click_id(~10 символов); - сохраняем в базу всё, что знаем о клике: страна, устройство, источник (UTM), время, IP, fingerprint;
- применяем правила маршрутизации smart-link, выбираем целевой URL;
- делаем 302-редирект на целевой URL, добавляя
?cid=<click_id>в querystring.
Click — это то, что мы знаем сразу. Дальше пользователь уходит на ваш лендинг, и мы не видим, что он там делает, пока вы не позовёте нас обратно через postback.
Conversion
Целевое действие на стороне вашего сайта или CRM: заказ оплачен, лид заполнил форму, регистрация подтверждена. То, ради чего вообще запускалась кампания. Конверсии бывают:
- С деньгами — известна стоимость заказа в копейках/центах, передаётся в параметре
value. - Без денег — лид, регистрация, подписка. Стоимость передаётся усреднённая или нулевая.
- Многошаговые — регистрация → подтверждение почты → первая покупка. Каждый шаг — отдельный postback с разным
event.
Postback (он же S2S callback)
HTTP-запрос (обычно POST или GET) от вашего сервера к нашему API в момент конверсии. Содержит click_id, тип события, и сумму. Мы получаем запрос и проставляем конверсию на ранее залогированный клик.
Почему «S2S» (server-to-server), а не просто «callback в браузере»? Потому что в браузере JS-вызов могут заблокировать (ITP, adblock, JS отключён), и атрибуция теряется. Server-to-server идёт между двумя серверами без участия браузера — его нельзя заблокировать на клиентской стороне.
Минимальный postback в нашем API:
POST https://api.tds.so/postback
Content-Type: application/json
Authorization: Bearer <ваш-API-токен>
{
"click_id": "k3p9x7q",
"event": "purchase",
"value": 4990,
"currency": "RUB"
}
Ответ — JSON-объект со статусом сшивки:
{
"status": "ok",
"click_id": "k3p9x7q",
"campaign": "spring-sale",
"matched": true
}
click_id (cid)
Уникальная подпись клика, ключ связи между click и conversion. У нас 10 символов из URL-safe алфавита (a-z, A-Z, 0-9, без I/l/0/O для разборчивости). Передаётся при редиректе в querystring как ?cid=k3p9x7q.
Ваша задача на лендинге: сохранить cid в момент захода и достать его в момент конверсии. Самый надёжный способ — localStorage:
// На посадочной, при загрузке
const cid = new URLSearchParams(location.search).get('cid');
if (cid) localStorage.setItem('tds_cid', cid);
// При конверсии (например, после оплаты)
const storedCid = localStorage.getItem('tds_cid');
// шлёте storedCid вместе с заказом на ваш бэкенд
// бэкенд шлёт postback с этим click_id на наш API
Macros (макросы)
Токены в шаблоне postback-URL, которые заменяются на реальные значения в момент срабатывания. Полезно, когда вы строите URL заранее и не хотите подставлять параметры на бэкенде вручную.
Полный список наших макросов:
{click_id}— идентификатор клика{event}— тип события (purchase, lead, registration){value}— стоимость конверсии (в минимальных единицах валюты, копейки/центы){currency}— код валюты ISO 4217 (RUB, USD, EUR){geo}— страна клика (ISO-3166: US, RU, DE){device}— тип устройства (mobile, desktop, tablet){source}— UTM-source, переданный при клике{campaign}— UTM-campaign{timestamp}— UNIX-time клика{ip}— IP-адрес клиента (без последнего октета для приватности)
Шаблон URL с макросами вы задаёте в настройках кампании, а мы подставляем значения автоматически:
https://your-crm.com/track?cid={click_id}&val={value}&geo={geo}
// при срабатывании превращается в:
https://your-crm.com/track?cid=k3p9x7q&val=4990&geo=RU
Hash / signature
Подпись postback-запроса, по которой принимающая сторона проверяет, что запрос реально пришёл от нас, а не от случайного скрипта, который угадал URL. Считается как HMAC-SHA256 от тела запроса с секретным ключом:
signature = hmac_sha256(
request_body,
secret_key
)
// и передаётся в заголовке
X-TDS-Signature: 3f1a8b9c...
На стороне получателя — пересчитайте подпись с тем же секретом и сравните с присланной. Совпало — postback от нас, можно доверять. Не совпало — отказ. Без подписи злоумышленник может вкидывать вам фейковые конверсии и портить аналитику.
9 из 10 интеграций в первый месяц не проверяют подпись postback на стороне приёма. «Это же приватный URL, кто угадает» — типичный аргумент. На практике конкурент, заметивший паттерн URL в HTML вашего сайта, накатит вам 50 000 фейковых конверсий за ночь и угробит атрибуционные отчёты на квартал. Подпись добавляется в 20 строк кода.
Полный сценарий end-to-end
Чтобы все термины сложились в картинку, проследим сквозной путь от клика по ссылке до постбэка:
# Шаг 1. Пользователь кликает по ссылке
GET https://tds.so/abc?utm_source=newsletter
# TDS логирует click, генерирует click_id=k3p9x7q, делает редирект:
HTTP/1.1 302 Found
Location: https://your-shop.com/promo?cid=k3p9x7q
# Шаг 2. На лендинге ваш JS сохраняет cid в localStorage:
localStorage.setItem('tds_cid', 'k3p9x7q');
# Шаг 3. Пользователь оплачивает заказ. Ваш бэкенд знает: order_id=12345,
# amount=49.90, cid=k3p9x7q (передан с фронта вместе с заказом)
# Шаг 4. Ваш бэкенд шлёт postback на наш API:
curl -X POST https://api.tds.so/postback \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"click_id": "k3p9x7q",
"event": "purchase",
"value": 4990,
"currency": "RUB"
}'
# Шаг 5. Мы получаем postback, находим click в базе, проставляем конверсию,
# отвечаем 200 OK. В дашборде кампании «newsletter» появляется +1 заказ
# на 49.90 RUB.
Чек-лист, когда postback не доходит
По 80% обращений в саппорт хватает одного из этих пунктов. Проверяйте по порядку:
- cid пропал на лендинге. Откройте DevTools → Application → Local Storage. Если
tds_cidтам есть после клика по нашей ссылке — JS работает. Если нет — баг сохранения. - cid не дошёл до бэкенда. Проверьте, что фронт реально передаёт
tds_cidв payload заказа. Чаще всего забывают добавить. - HTTP-запрос не уходит. Логируйте на своём бэкенде сам факт отправки postback. Часто из-за неправильного URL или firewall запрос вообще не делается.
- Запрос уходит, но мы отдаём ошибку. Проверьте ответ нашего API: HTTP-код + тело. 401 — проблема с токеном, 422 — невалидный JSON или missing fields, 404 — click_id не найден (возможно, истёк, у нас retention 90 дней).
- Postback дошёл, но конверсия не появилась в отчёте. Проверьте кампанию-фильтр в дашборде — возможно, смотрите не на ту. Конверсия попала, просто в другую кампанию.
Для удобства дебага у нас есть «sandbox»-режим: ваши тестовые postback-и логируются, но не влияют на статистику кампании. Включается в настройках интеграции, и в логах виден весь raw-payload вашего запроса.
Вывод
Postback — это server-to-server HTTP-запрос, который сшивает click и conversion. Ключ связи — click_id. Макросы упрощают шаблонизацию URL. Подпись (HMAC-SHA256) защищает от подделки. Если postback не доходит — пройдитесь по 5-пунктовому чек-листу в начале (cid, бэкенд, HTTP, ответ API, фильтр в дашборде), и в 80% случаев проблема найдётся за 5 минут.
Связано: если непонятно, почему вообще нельзя обойтись JS-пикселем в браузере — см. куда теряется UTM. ITP, webview и AMP — три причины, по которым клиентская атрибуция в 2026-м недостаточна, и server-side через postback стал стандартом.