Оформление
Передача конверсий (runtime)
Этот метод позволяет программно сообщить Sales Ninja, что клиент достиг цели. Применяется в случаях, когда достижение цели происходит не на сайте: в мобильном приложении, в CRM, в backend-сценарии, при импорте исторических данных и т.п.
Функционал — аналог офлайн-конверсий в Яндекс.Метрике.
Два способа сообщить об одной цели
Вызываемая вручную цель — одна сущность с одним идентификатором, у которой два входа. Выбирается не вид цели, а место, откуда приходит событие:
| Откуда приходит событие | Чем сообщаете | Что передаёте |
|---|---|---|
| Сайт, на котором стоит бандл | ninja('reachGoal', ...) | id цели; при желании выручку и прибыль |
| Сервер, CRM, мобильное приложение | POST goals/reach — эта страница | id цели, customerId, sessionId; при желании деньги и время |
Разница только в идентификаторах человека и визита. На сайте их подставляет бандл — он уже знает и customerId, и sessionId, поэтому в JS-вызове этих полей нет. В запросе оба значения передаёте вы: если событие начиналось на сайте, возьмите их на странице и сохраните рядом с заявкой (как именно).
Описание метода
http
POST https://api.sales-ninja.me/public/{source}/v1.0/goals/reach
Headers:
X-SN-TOKEN: <ваш токен проекта>
Content-Type: application/jsonПараметр {source} — mobile или backend (см. начало работы). Любое другое значение → 400.
Тело запроса
json
{
"goalId": "299837db-9b51-45a4-996b-844d3fd05bbd",
"customerId": "user-42-from-your-system",
"sessionId": "0f6b4e0f-2d1a-4a02-9a3f-6a1f1d5f77c2",
"createdOn": "2026-04-15T12:23:25.367Z",
"ip": "185.237.80.128",
"params": {
"revenue": "12350",
"netProfit": "900"
}
}Примеры тела
Полный вариант:
json
{
"goalId": "299837db-9b51-45a4-996b-844d3fd05bbd",
"customerId": "2eccd743-4164-4892-a416-dc5c16f44971",
"sessionId": "0f6b4e0f-2d1a-4a02-9a3f-6a1f1d5f77c2",
"createdOn": "2026-04-15T12:23:25.367Z",
"ip": "185.237.80.128",
"params": {
"revenue": "12350",
"netProfit": "900"
}
}Минимально достаточный вариант:
json
{
"goalId": "299837db-9b51-45a4-996b-844d3fd05bbd",
"customerId": "2eccd743-4164-4892-a416-dc5c16f44971"
}Поля
goalId (обязательно)
Уникальный идентификатор цели в системе Sales Ninja (не в вашей CRM/аналитике). Это GUID, например 299837db-9b51-45a4-996b-844d3fd05bbd. Получить его можно из адресной строки редактора цели:
text
https://app.sales-ninja.me/goal/<id будет тут>/editioncustomerId (обязательно)
Тот же строковый идентификатор, который вы передаёте в personalizations/apply и ab-tests/apply.
На сайте с бандлом:
js
const customerId = await ninja('getCustomerIdAsync')Для серверной интеграции — сохраняйте свой стабильный id рядом с записью о клиенте.
sessionId (важно)
Визит, в котором произошло действие. Это то, что связывает конверсию с рекламой: без sessionId событие не свяжется с кампанией, которая его привела, и останется без источника.
На сайте с бандлом:
js
const sessionId = await ninja('getSessionIdAsync')Порядок один и тот же: на странице берёте customerId и sessionId, сохраняете их рядом с заявкой у себя, и возвращаете вместе с конверсией, когда она подтвердилась. Если действие не начиналось на сайте (звонок, приложение без бандла), поле можно не передавать — конверсия примется, но без привязки к визиту.
createdOn (опционально)
Время совершения конверсии в UTC (ISO 8601). Поле на проводе — createdOn, не createdOnUtc. Если не передано — берётся текущее серверное время.
ip (опционально)
IP пользователя, совершившего конверсию. Для {source}=mobile при отсутствии ip в теле подставляется IP из заголовков соединения; для backend — только из тела.
params (опционально)
Плоский словарь строк для обогащения конверсии (выручка, прибыль, UTM-подобные метки и т.п.).
Ключи revenue и netProfit записываются как выручка и прибыль самой конверсии — так же, как при вызове ninja('reachGoal', ...) на сайте. Регистр этих двух ключей не важен. Остальные ключи остаются признаками конверсии и деньгами не становятся.
Значения обязательно строки:
"12350", не12350. Максимум 48 ключей.
Поля customUserScopeParams и customPageScopeParams не поддерживаются — передайте нужные ключи через params.
Ответ
При успехе — 200 OK с пустым телом. Конверсия принимается асинхронно и появится в аналитике через короткое время (обычно секунды; на большом потоке — минуты).
При ошибке — см. Runtime-ошибки.
Примеры кода
bash
curl -X POST "https://api.sales-ninja.me/public/backend/v1.0/goals/reach" \
-H "X-SN-TOKEN: $SN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"goalId": "299837db-9b51-45a4-996b-844d3fd05bbd",
"customerId": "2eccd743-4164-4892-a416-dc5c16f44971",
"sessionId": "0f6b4e0f-2d1a-4a02-9a3f-6a1f1d5f77c2",
"createdOn": "2026-04-15T12:23:25Z",
"params": {"revenue": "12350", "netProfit": "900"}
}'js
async function reachGoal({ goalId, customerId, sessionId, revenue, netProfit }) {
const res = await fetch(
`https://api.sales-ninja.me/public/backend/v1.0/goals/reach`,
{
method: 'POST',
headers: {
'X-SN-TOKEN': process.env.SN_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
goalId,
customerId,
sessionId,
createdOn: new Date().toISOString(),
params: {
revenue: String(revenue),
netProfit: String(netProfit),
},
}),
}
);
if (!res.ok) {
throw new Error(`reachGoal failed: ${res.status} ${await res.text()}`);
}
}python
import os, requests
from datetime import datetime, timezone
def reach_goal(goal_id: str, customer_id: str, revenue: float, net_profit: float) -> None:
r = requests.post(
"https://api.sales-ninja.me/public/backend/v1.0/goals/reach",
headers={"X-SN-TOKEN": os.environ["SN_TOKEN"]},
json={
"goalId": goal_id,
"customerId": customer_id,
"createdOn": datetime.now(timezone.utc).isoformat(),
"params": {
"revenue": str(revenue),
"netProfit": str(net_profit),
},
},
timeout=15,
)
r.raise_for_status()Идемпотентность и дубли
API не дедуплицирует одинаковые конверсии автоматически. Если интегратор может ретраить запрос — сохраняйте, какие конверсии уже отправлены. Дубликат с тем же customerId/goalId будет принят как ещё одна конверсия.