Рецепти вебхуків
Сповіщення показують, що відбувається на ринку. Вебхуки й Telegram — це спосіб діяти, з власними інструментами й власними акаунтами. Ми ніколи не торгуємо за вас, ніколи не зберігаємо кошти й ніколи не просимо API-ключі з правом торгівлі.
Перш ніж почати
- На сторінці «Сповіщення» додайте власний JSON-вебхук. Він входить до VIP Quant і пробного періоду.
- Скопіюйте секрет підпису. Він показується один раз.
- Натисніть «Надіслати тест» і перевірте, що ваш ендпоінт його отримав.
- Спрямуйте правило на цей канал. Сповіщення за замовчуванням теж надходить на нього, поки ввімкнене.
Відповідайте будь-яким 2xx протягом 8 секунд. Невдалі надсилання повторюються з тим самим id, тож дедуплікуйте за ним. Кожен фрагмент нижче протестовано на реальній підписаній доставці. Ви відповідаєте за все, що автоматизуєте на основі цих подій.
Події
opportunity.alert: маршрут відповів правилу або сповіщенню за замовчуванням. Для сповіщення за замовчуваннямrule.idдорівнюєdefault.listing.alert: щойно залістили ринок, одна подія на лістинг.opportunity.closed: маршрут, про який ви отримали сповіщення, закрився, якщо правило цього вимагає.position.alert: щось сталося з відстежуваною угодою.notice.alert: біржа оголосила делістинг, призупинення введення або виведення, технічні роботи або лістинг за сповіщенням «Сповіщення за замовчуванням».trackedозначає, що це стосується угоди, яку ви відстежуєте.watch.alert: гаманець, за яким ви спостерігаєте, отримав або надіслав переказ, за сповіщенням «Сповіщення за замовчуванням».event.txдорівнює null, коли хеш транзакції невідомий.alert.test: з «Надіслати тест», з"test": trueі фіктивною монетою TEST.
{
"id": "evt_7c1e...",
"type": "listing.alert",
"test": false,
"sentAt": "2026-11-02T10:00:00.000Z",
"rule": { "id": "...", "name": "New listings" },
"listing": { "coin": "ABC", "venue": "Binance", "kind": "spot", "symbol": "ABCUSDT", "listedMs": 1793000000000 },
"window": { "sizeUsd": 1000, "openMs": 4000, "avgWindowMs": null, "avgPeakNetBps": null, "windowSamples": 0 },
"opportunity": { "id": "ABC|Binance:spot|OKX:spot", "coin": "ABC", "...": "..." }
}Повний перелік полів — у документації вебхуків.
Перевірка підпису
Кожен запит містить X-ArbitrageX-Timestamp і X-ArbitrageX-Signature, тобто sha256= плюс hex HMAC-SHA256 від timestamp.rawBody. Перевіряйте його на сирих байтах до парсингу, бо розпарсений і повторно закодований JSON не збігатиметься. Відхиляйте мітки часу, що відрізняються від поточного більш ніж на 5 хвилин, щоб запобігти повторам.
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, headers, secret) {
const ts = headers["x-arbitragex-timestamp"];
if (!(Math.abs(Date.now() / 1000 - Number(ts)) <= 300)) return false;
const expected =
"sha256=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const given = Buffer.from(String(headers["x-arbitragex-signature"]));
const wanted = Buffer.from(expected);
return given.length === wanted.length && timingSafeEqual(given, wanted);
}Python
import hashlib
import hmac
import time
def verify(raw_body: bytes, headers: dict, secret: str) -> bool:
ts = headers.get("x-arbitragex-timestamp", "")
try:
if abs(time.time() - int(ts)) > 300:
return False
except ValueError:
return False
signed = ts.encode() + b"." + raw_body
expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected.encode(), headers.get("x-arbitragex-signature", "").encode())Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"math"
"net/http"
"strconv"
"time"
)
func Verify(rawBody []byte, headers http.Header, secret string) bool {
ts := headers.Get("X-ArbitrageX-Timestamp")
unix, err := strconv.ParseInt(ts, 10, 64)
if err != nil || math.Abs(float64(time.Now().Unix()-unix)) > 300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(ts + "."))
mac.Write(rawBody)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(headers.Get("X-ArbitrageX-Signature")))
}Інструменти виконання
Кожен приклад нижче — це тіло, яке очікує інструмент, складене на основі нашого стандартного payload: кожне значення в кутових дужках — це наше поле, яке треба туди підставити. Зберіть його в кроці Zapier чи Make після catch webhook або у власному ретрансляторі, як-от приймач із розділу «Запуск власного скрипта», і надішліть на URL вебхука інструмента. Значення великими літерами — ваші власні. До інструмента мають доходити лише opportunity.alert і listing.alert: відфільтруйте test і всі інші типи, щоб повідомлення про закриття не могло відкрити угоду. Ми ніколи не торгуємо: що інструмент робить із повідомленням, вирішуєте ви й інструмент.
Кожен інструмент із часом змінює власні поля. Звірте приклад з актуальною документацією інструмента, перш ніж на нього покладатися.
3Commas signal bot
Для власного вебхука сигнального бота 3Commas, із секретом і UUID бота з налаштувань повідомлень бота. Сигнальний бот торгує на одній біржі, тож обмежте правило зв’язками, що купують там, за допомогою фільтра бірж у правилі.
{
"secret": "YOUR_3COMMAS_SECRET",
"bot_uuid": "YOUR_BOT_UUID",
"max_lag": "300",
"timestamp": "<sentAt>",
"trigger_price": "<opportunity.buy.price>",
"tv_exchange": "<opportunity.buy.venue in capitals, e.g. BINANCE>",
"tv_instrument": "<opportunity.coin>USDT",
"action": "enter_long"
}Hummingbot
Hummingbot приймає команди через власні інтерфейси, а не вебхук, тож це надсилається у вашу невелику скрипт-стратегію або в міст до його MQTT-інтерфейсу. Використовуйте власну назву вашого конектора.
{
"connector": "<opportunity.buy.venue in lower case, e.g. binance>",
"trading_pair": "<opportunity.coin>-USDT",
"side": "BUY",
"price": <opportunity.buy.price>,
"amount_usd": <window.sizeUsd>,
"hedge_connector": "<opportunity.sell.venue in lower case>",
"hedge_price": <opportunity.sell.price>,
"net_bps": <opportunity.netBps>,
"event_id": "<id>"
}Cornix signal
Простий текст у форматі сигналів Cornix для адреси вебхука, яку Cornix видає боту TradingView. Задайте власний стоп і звірте формат з актуальним посібником Cornix.
#<opportunity.coin>/USDT Exchanges: <opportunity.buy.venue> Signal Type: Regular (Long) Entry Targets: 1) <opportunity.buy.price> Take-Profit Targets: 1) <opportunity.sell.price> Stop Targets: 1) YOUR_STOP_PRICE
TradingView-style JSON
Назви полів, які надсилає сповіщення TradingView, для інструментів, створених приймати сповіщення TradingView.
{
"ticker": "<opportunity.coin>USDT",
"exchange": "<opportunity.buy.venue in capitals>",
"action": "buy",
"price": <opportunity.buy.price>,
"time": "<sentAt>",
"message": "<opportunity.coin> <opportunity.buy.venue> to <opportunity.sell.venue>, net <opportunity.netBps> bps"
}Flat JSON
Усі показники на верхньому рівні — для таблиці, n8n або бази даних.
{
"id": "<id>",
"coin": "<opportunity.coin>",
"net_bps": <opportunity.netBps>,
"net_usd_1k": <opportunity.netUsd.1000>,
"max_size_usd": <opportunity.maxSizeUsd>,
"buy_exchange": "<opportunity.buy.venue>",
"buy_price": <opportunity.buy.price>,
"buy_url": "<opportunity.buy.url>",
"sell_exchange": "<opportunity.sell.venue>",
"sell_price": <opportunity.sell.price>,
"sell_url": "<opportunity.sell.url>"
}Інші канали
Кожен канал нижче налаштовується на сторінці «Сповіщення», має кнопку «Надіслати тест» і виглядає як повідомлення в Telegram, мовою акаунта. Правила, паузи, тихі години, ліміт частоти й автопризупинення працюють однаково на всіх.
- Slack (від Pro Desk): додайте застосунок Incoming Webhooks до каналу в Slack і вставте URL
hooks.slack.com, який він видасть. - ntfy (від Starter): безкоштовні сповіщення на телефоні й комп’ютері. Вставте URL топіка, наприклад
https://ntfy.sh/your-topic, або топіка на власному сервері, і підпишіться на той самий топік у застосунку ntfy. Будь-хто, хто знає назву публічного топіка, може його читати, тож оберіть довгу випадкову назву або додайте токен доступу для захищеного топіка. Кожне сповіщення має кнопки для обох бірж. - Email-дайджест (від Starter): один лист на адресу вашого акаунта з усіма сповіщеннями від попереднього, щогодини на початку години або щодня о 08:00 UTC. Якщо нічого нового немає, нічого не надсилається.
WhatsApp не має безкоштовного способу, яким сервіс міг би вам писати, тож його немає. Для всього іншого власний вебхук — це простий JSON через https.
Публікація в Slack
Cloudflare Worker без власного сервера. Задайте ARBITRAGEX_SECRET і SLACK_WEBHOOK_URL як секрети, а потім використайте URL Worker як канал. Той самий код працює будь-де з Web Crypto.
async function verify(rawBody, headers, secret) {
const ts = headers.get("x-arbitragex-timestamp");
if (!(Math.abs(Date.now() / 1000 - Number(ts)) <= 300)) return false;
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["verify"],
);
const given = (headers.get("x-arbitragex-signature") ?? "").replace("sha256=", "");
if (!/^[0-9a-f]{64}$/.test(given)) return false;
const bytes = Uint8Array.from(given.match(/../g), (pair) => parseInt(pair, 16));
return crypto.subtle.verify("HMAC", key, bytes, encoder.encode(`${ts}.${rawBody}`));
}
export default {
async fetch(request, env) {
const rawBody = await request.text();
if (!(await verify(rawBody, request.headers, env.ARBITRAGEX_SECRET))) {
return new Response("bad signature", { status: 401 });
}
const event = JSON.parse(rawBody);
if (event.type === "opportunity.alert" || event.type === "listing.alert") {
const { coin, buy, sell, netBps } = event.opportunity;
await fetch(env.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `${coin}: ${buy.venue} to ${sell.venue}, net ${netBps.toFixed(1)} bps`,
}),
});
}
return new Response(null, { status: 204 });
},
};Ведення таблиці
Додає по рядку на кожне сповіщення до CSV, який можна відкрити в будь-якій таблиці, лише за допомогою стандартної бібліотеки Python. Розмістіть його за https: ми надсилаємо лише на публічні https-адреси.
import csv
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
import hashlib
import hmac
import time
def verify(raw_body: bytes, headers: dict, secret: str) -> bool:
ts = headers.get("x-arbitragex-timestamp", "")
try:
if abs(time.time() - int(ts)) > 300:
return False
except ValueError:
return False
signed = ts.encode() + b"." + raw_body
expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected.encode(), headers.get("x-arbitragex-signature", "").encode())
SECRET = "your signing secret"
seen = set()
class Receiver(BaseHTTPRequestHandler):
def do_POST(self):
raw_body = self.rfile.read(int(self.headers["Content-Length"]))
headers = {key.lower(): value for key, value in self.headers.items()}
if not verify(raw_body, headers, SECRET):
self.send_response(401)
self.end_headers()
return
self.send_response(204)
self.end_headers()
event = json.loads(raw_body)
if event["test"] or event["id"] in seen or "opportunity" not in event:
return
seen.add(event["id"])
row = event["opportunity"]
with open("alerts.csv", "a", newline="") as file:
csv.writer(file).writerow(
[event["sentAt"], event["type"], row["coin"], row["buy"]["venue"], row["sell"]["venue"], row["netBps"]]
)
if __name__ == "__main__":
HTTPServer(("127.0.0.1", 8000), Receiver).serve_forever()Запуск власного скрипта
Приймач без залежностей, що відповідає одразу й передає кожне сповіщення одній вашій функції. Що робить ця функція — вам писати й вам за це відповідати. Сповіщення — це оцінки на основі ринкових даних, і маршрут може закритися раніше, ніж щось, що ви запустите, дійде.
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, headers, secret) {
const ts = headers["x-arbitragex-timestamp"];
if (!(Math.abs(Date.now() / 1000 - Number(ts)) <= 300)) return false;
const expected =
"sha256=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const given = Buffer.from(String(headers["x-arbitragex-signature"]));
const wanted = Buffer.from(expected);
return given.length === wanted.length && timingSafeEqual(given, wanted);
}
const seen = new Set();
function onAlert(event) {
// Your code goes here. You decide what happens next, and we never see it.
console.log(event.type, event.opportunity.coin, event.opportunity.netBps);
}
export const server = createServer((req, res) => {
const parts = [];
req.on("data", (part) => parts.push(part));
req.on("end", () => {
const rawBody = Buffer.concat(parts).toString("utf8");
if (!verify(rawBody, req.headers, process.env.ARBITRAGEX_SECRET)) {
return res.writeHead(401).end();
}
const event = JSON.parse(rawBody);
// Answer first: anything slower than 8 seconds counts as a failed delivery.
res.writeHead(204).end();
if (event.test || seen.has(event.id)) return;
seen.add(event.id);
if (event.type === "opportunity.alert" || event.type === "listing.alert") onAlert(event);
});
});Zapier і Make
Власний вебхук — це простий JSON POST, тож catch webhook URL з будь-якого з них підходить як канал. Додайте крок фільтра за type і test, потім зіставте поля, як-от opportunity.coin і opportunity.netBps. Ці кроки не перевіряють наш підпис, тож вважайте catch URL паролем і не запускайте там нічого, що ви не дозволили б запустити незнайомцю.
Telegram
Якщо вам потрібні лише сповіщення на телефоні, пропустіть усе це. Натисніть «Підключити Telegram» на сторінці «Сповіщення», а потім Start у чаті, що відкриється. Дивіться документацію сповіщень.