# Передача конверсий (runtime)

Этот метод позволяет программно сообщить Sales Ninja, что какой-то клиент достиг цели. Применяется в случаях, когда достижение цели происходит не на сайте: в мобильном приложении, в CRM, в backend-сценарии, при импорте исторических данных и т.п.

Функционал — аналог офлайн-конверсий в Яндекс.Метрике (opens new window).

# Описание метода

POST https://api.sales-ninja.me/public/{source}/v1.0/goals/reach
Headers:
  X-SN-TOKEN: <ваш токен проекта>
  Content-Type: application/json

Параметр {source} — идентификатор внешнего источника (мобильное приложение, серверный сценарий, CRM-импорт и т.п.). Допустимые значения возвращает поддержка; неизвестный source отдаёт 400.

# Тело запроса

{
  "ip": "<IP клиента (опционально)>",
  "goalId": "<id цели в Sales Ninja (обязательно)>",
  "customerId": "<id клиента в Sales Ninja (обязательно)>",
  "sessionId": "<id сессии клиента (опционально)>",
  "createdOnUtc": "<время конверсии по UTC (опционально)>",
  "customUserScopeParams": { "...": "..." },
  "customPageScopeParams": { "...": "..." }
}

# Примеры тела

Полный вариант:

{
  "ip": "185.237.80.128",
  "goalId": "299837db-9b51-45a4-996b-844d3fd05bbd",
  "customerId": "2eccd743-4164-4892-a416-dc5c16f44971",
  "sessionId": "123212db-9b51-a416-996b-844d3fd09b51",
  "createdOnUtc": "2026-04-15T12:23:25.367Z",
  "customUserScopeParams": {
    "revenue": "12350",
    "netProfit": "900"
  },
  "customPageScopeParams": {}
}

Минимально достаточный вариант:

{
  "goalId": "299837db-9b51-45a4-996b-844d3fd05bbd",
  "customerId": "2eccd743-4164-4892-a416-dc5c16f44971",
  "customPageScopeParams": {
    "revenue": "12350",
    "netProfit": "900"
  }
}

# Поля

# ip

IP-адрес пользователя, совершившего конверсию. Необязательно, но желательно передавать — он используется для обогащения данных (география, антибот).

# goalId

Уникальный идентификатор цели в системе Sales Ninja (не в вашей CRM/аналитике). Это GUID (opens new window), например 299837db-9b51-45a4-996b-844d3fd05bbd. Получить его можно из адресной строки редактора цели:

https://app.sales-ninja.me/goal/<id будет тут>/edition

# customerId

Идентификатор клиента в системе Sales Ninja. Получается на сайте через JS:

// Новый асинхронный вариант (рекомендуется)
ninja('getCustomerIdAsync').then(id => {
  // id — это customerId
})

// Или в async-контексте
const customerId = await ninja('getCustomerIdAsync')

Если ваша интеграция не на сайте (например, серверный импорт из CRM) — сохраняйте customerId рядом с записью о клиенте при первой выдаче и используйте его потом при отправке конверсий.

# sessionId

Идентификатор сессии клиента. Тоже из JS:

const sessionId = await ninja('getSessionIdAsync')

# createdOnUtc

Время совершения конверсии в UTC. Если не передано — берётся текущее серверное время.

# customUserScopeParams

Пользовательские параметры клиента в виде объекта ключ-значение. Используются для обогащения данных, в т.ч. для передачи выручки и прибыли.

Значения параметров обязательно строки: "12350", не 12350.

# customPageScopeParams

Пользовательские параметры страницы. Тот же формат, тот же строковый контракт.

# Ответ

При успехе — 200 OK с пустым телом. Конверсия появится в системе через какое-то время (обычно секунды; на большом потоке могут быть минутные задержки).

При ошибке — стандартный Problem Details с понятным code и hint.

# Примеры кода

curl -X POST "https://api.sales-ninja.me/public/server/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",
    "createdOnUtc": "2026-04-15T12:23:25Z",
    "customUserScopeParams": {"revenue": "12350", "netProfit": "900"}
  }'
async function reachGoal({ goalId, customerId, revenue, netProfit }) {
  const res = await fetch(
    `https://api.sales-ninja.me/public/server/v1.0/goals/reach`,
    {
      method: 'POST',
      headers: {
        'X-SN-TOKEN': process.env.SN_TOKEN,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        goalId,
        customerId,
        createdOnUtc: new Date().toISOString(),
        customUserScopeParams: {
          revenue: String(revenue),
          netProfit: String(netProfit),
        },
      }),
    }
  );
  if (!res.ok) {
    throw new Error(`reachGoal failed: ${res.status} ${await res.text()}`);
  }
}
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/server/v1.0/goals/reach",
        headers={"X-SN-TOKEN": os.environ["SN_TOKEN"]},
        json={
            "goalId": goal_id,
            "customerId": customer_id,
            "createdOnUtc": datetime.now(timezone.utc).isoformat(),
            "customUserScopeParams": {
                "revenue": str(revenue),
                "netProfit": str(net_profit),
            },
        },
        timeout=15,
    )
    r.raise_for_status()

# Идемпотентность и дубли

API сейчас не дедуплицирует одинаковые конверсии автоматически. Если ваш интегратор может ретрить запрос — сохраняйте, какие конверсии уже улетели, и не отправляйте их повторно. Дубликат с тем же customerId/goalId будет принят и обработан как ещё одна конверсия.