Интеграция с сервисами
1Интеграция с сервисами
Как соединить LootX с TradingView, Telegram, таблицами и собственными сервисами: вебхуки, подпись запросов, идемпотентность и обработка ошибок.
Интеграция нужна в трёх случаях: сигнал приходит снаружи и должен превратиться в действие; событие происходит внутри и должно уехать наружу; данные нужно забрать по расписанию. LootX закрывает все три — входящими вебхуками, исходящими событиями и REST API.
Карта возможностей
TradingView, n8n, Make, собственный скрипт — сигнал превращается в ордер.
Исходящие событияСделки, стопы и алерты уходят в ваш сервис, Telegram или таблицу.
REST и WebSocketПрямой доступ к данным и ордерам из своего кода.
Входящие вебхуки
Секрет и персональный URL лежат в кабинете: откройте поиск по документации на ⌘/Ctrl+K или перейдите в Кабинет → Интеграции. Персональный URL выглядит так:
https://api.lootx.trade/v1/hooks/in/9f3c1a7e-0b2d-4c8a-9f01-6e2b7d4c5a10Он не секрет сам по себе — секретом является подпись. URL можно вставить в любой сервис, умеющий слать HTTP POST.
/v1/hooks/in/:hookIdПринимает сигнал и выполняет действие, описанное в теле. Отвечает 202 Accepted, когда сигнал принят к исполнению, и 200 OK, когда он был уже обработан ранее (повтор).
Заголовки
| Заголовок | Обязателен | Описание |
|---|---|---|
Content-Type |
да | application/json |
X-LootX-Signature |
да | sha256=<hex> — HMAC-SHA256 тела запроса |
X-LootX-Timestamp |
да | Unix-время в секундах; допуск ±300 с |
Тело
| Поле | Тип | Описание |
|---|---|---|
action |
string | order, close, cancel-all или notify |
exchange |
string | Код биржи: bybit, binance, okx |
symbol |
string | Инструмент, например BTCUSDT |
side |
string | buy или sell — для action: order |
qty |
string | Объём. Строка, не число: см. предупреждение ниже |
orderType |
string | market или limit, по умолчанию market |
price |
string | Цена для лимитной заявки |
stopLoss |
string | Абсолютная цена стопа |
takeProfit |
string | Абсолютная цена тейка |
requestId |
string | Идентификатор для идемпотентности, до 64 символов |
comment |
string | Попадёт в журнал сделок |
Подпись запроса
Подпись считается от точного тела запроса — той же последовательности байт, что уходит в сеть. Не пересобирайте JSON перед подписью.
import crypto from "node:crypto";
const secret = process.env.LOOTX_HOOK_SECRET!;
const body = JSON.stringify({
action: "order",
exchange: "bybit",
symbol: "BTCUSDT",
side: "buy",
qty: "0.01",
stopLoss: "61250",
requestId: crypto.randomUUID(),
});
const ts = Math.floor(Date.now() / 1000).toString();
const signature = crypto
.createHmac("sha256", secret)
.update(`${ts}.${body}`)
.digest("hex");
const res = await fetch(HOOK_URL, {
method: "POST",
headers: {
"content-type": "application/json",
"x-lootx-timestamp": ts,
"x-lootx-signature": `sha256=${signature}`,
},
body,
});import hmac, hashlib, json, os, time, uuid, requests
secret = os.environ["LOOTX_HOOK_SECRET"].encode()
body = json.dumps({
"action": "order",
"exchange": "bybit",
"symbol": "BTCUSDT",
"side": "buy",
"qty": "0.01",
"stopLoss": "61250",
"requestId": str(uuid.uuid4()),
}, separators=(",", ":")) # без пробелов: подписываем то, что отправляем
ts = str(int(time.time()))
sig = hmac.new(secret, f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()
requests.post(HOOK_URL, data=body, timeout=10, headers={
"content-type": "application/json",
"x-lootx-timestamp": ts,
"x-lootx-signature": f"sha256={sig}",
})$secret = getenv('LOOTX_HOOK_SECRET');
$body = json_encode([
'action' => 'order',
'exchange' => 'bybit',
'symbol' => 'BTCUSDT',
'side' => 'buy',
'qty' => '0.01',
'requestId' => bin2hex(random_bytes(8)),
], JSON_UNESCAPED_SLASHES);
$ts = (string) time();
$sig = hash_hmac('sha256', "$ts.$body", $secret);
$ch = curl_init(HOOK_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-LootX-Timestamp: $ts",
"X-LootX-Signature: sha256=$sig",
],
]);
curl_exec($ch);TradingView
TradingView не умеет подписывать запросы — он шлёт произвольный текст на URL. Поэтому для алертов TradingView в кабинете включается отдельный режим: Интеграции → TradingView → Создать канал. Канал выдаёт URL с одноразово-длинным идентификатором и требует, чтобы в теле присутствовал ваш channelToken.
Создайте канал
Кабинет → Интеграции → TradingView. Скопируйте URL и токен.
Опишите правила исполнения
Здесь же задаются ограничения: какие инструменты разрешены, максимальный объём, обязателен ли стоп, что делать при встречном сигнале. Эти правила сильнее содержимого алерта — сигнал не сможет превысить заданный лимит.
Вставьте сообщение в алерт
В окне алерта TradingView выберите Webhook URL и вставьте тело:
{
"channelToken": "chn_8d2f…",
"action": "order",
"exchange": "bybit",
"symbol": "{{ticker}}",
"side": "{{strategy.order.action}}",
"qty": "{{strategy.order.contracts}}",
"requestId": "{{timenow}}-{{ticker}}",
"comment": "{{strategy.order.comment}}"
}Проверьте на бумажном счёте
У канала есть переключатель Тестовый режим: сигналы проходят весь путь, ордера не отправляются, а результат виден в журнале.
Идемпотентность
requestId — единственное, что отделяет «повторили запрос, потому что не получили ответ» от «открыли вторую позицию».
| Ситуация | Без requestId |
С requestId |
|---|---|---|
| Таймаут сети, клиент повторил | два ордера | один ордер, второй ответ — 200 с тем же результатом |
| Сервис отправил дубль алерта | два ордера | один ордер |
| Осознанный повтор через сутки | новый ордер | новый ордер (окно идемпотентности — 24 часа) |
{
"accepted": true,
"duplicate": false,
"requestId": "2f0f9b1c-…",
"orderId": "1f0c9d2a-6b41-4f7e-9a02-5c8b1e3d7a44",
"status": "filled",
"filledQty": "0.01",
"avgPrice": "61840.5"
}Исходящие события
Обратное направление: LootX сообщает вашему сервису, что произошло.
<вашМы отправляем событие POST-запросом с теми же заголовками подписи, что ждём от вас. Ответ 2xx считается доставкой; любой другой код или таймаут в 10 секунд — поводом для повтора.
Повторы
Пять попыток с задержками 10 с, 1 мин, 5 мин, 30 мин, 2 ч. После пятой неудачи событие отмечается как недоставленное и остаётся в журнале интеграции — оттуда его можно отправить вручную.
| Событие | Когда | Полезная нагрузка |
|---|---|---|
order.filled |
Заявка исполнена целиком | ордер, цена, комиссия |
order.partially_filled |
Частичное исполнение | ордер, исполненный объём |
position.closed |
Позиция закрыта | итоговый P&L, причина |
stop.triggered |
Сработал стоп | цена срабатывания |
alert.fired |
Сработал ценовой алерт | инструмент, условие |
risk.limit_reached |
Достигнут дневной лимит убытка | лимит, факт |
{
"id": "evt_01J8X…",
"type": "position.closed",
"createdAt": "2026-09-22T11:04:19.442Z",
"data": {
"exchange": "bybit",
"symbol": "BTCUSDT",
"side": "long",
"qty": "0.01",
"entryPrice": "61840.5",
"exitPrice": "62310.0",
"realizedPnl": "4.695",
"reason": "take_profit"
}
}Пример приёмника на Fastify
import Fastify from "fastify";
import crypto from "node:crypto";
const app = Fastify();
const secret = process.env.LOOTX_HOOK_SECRET!;
// Нужно сырое тело: подпись считается от байтов, а не от разобранного объекта.
app.addContentTypeParser("application/json", { parseAs: "buffer" }, (_req, body, done) =>
done(null, body),
);
app.post("/lootx", async (req, reply) => {
const raw = req.body as Buffer;
const ts = String(req.headers["x-lootx-timestamp"] ?? "");
const got = String(req.headers["x-lootx-signature"] ?? "").replace(/^sha256=/, "");
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
return reply.code(401).send({ error: "stale timestamp" });
}
const want = crypto.createHmac("sha256", secret).update(`${ts}.${raw}`).digest("hex");
const ok =
got.length === want.length &&
crypto.timingSafeEqual(Buffer.from(got, "hex"), Buffer.from(want, "hex"));
if (!ok) return reply.code(401).send({ error: "bad signature" });
const event = JSON.parse(raw.toString("utf8"));
// Отвечаем сразу, обрабатываем потом: медленный приёмник провоцирует повторы.
queueMicrotask(() => handle(event));
return reply.code(200).send({ ok: true });
});Telegram и таблицы
Для двух самых частых сценариев готовые каналы есть в кабинете — писать приёмник не нужно.
Интеграции → Telegram → Подключить, дальше бот попросит подтвердить аккаунт. Выберите, какие события слать и в какой чат; поддерживаются личные сообщения и группы, в группе бот должен быть администратором.
Интеграции → Google Sheets. Каждая закрытая сделка добавляется строкой: время, инструмент, направление, объём, цены входа и выхода, комиссия, P&L, комментарий. Удобно для собственной статистики и налоговой отчётности.
Универсальный вариант: узел Webhook как приёмник наших событий и HTTP Request для отправки сигналов нам. Подпись считается узлом Crypto (HMAC SHA256) от строки timestamp.body.
Лимиты
| Направление | Лимит | При превышении |
|---|---|---|
| Входящие вебхуки | 60 запросов/мин на канал | 429, заголовок Retry-After |
| REST, чтение | 600 запросов/мин на ключ | 429 |
| REST, торговля | 120 запросов/мин на ключ | 429 |
| WebSocket-подписки | 200 каналов на соединение | ошибка подписки 4029 |
за последние 90 дней, с учётом повторов
от приёма сигнала до выставленного ордера
Чек-лист перед боем
Секреты не в коде
Секрет вебхука и API-ключ — в переменных окружения или менеджере секретов, а не в репозитории.
Тестовый режим пройден
Канал отработал на тестовом режиме, в журнале интеграции видны ожидаемые действия.
Стоп в каждом сигнале
stopLoss передаётся всегда, а в правилах канала включён обязательный стоп.
Ограничения выставлены
Максимальный объём, белый список инструментов и дневной лимит убытка настроены так, что ошибка в вашем коде не превратится в потерю депозита.
Есть наблюдение
Событие risk.limit_reached и недоставленные вебхуки приходят туда, где вы их увидите.
https://docs.lootx.trade/ru/api/integrations