Resep webhook
Alert memberi tahu apa yang sedang dilakukan pasar. Webhook dan Telegram adalah cara Anda bertindak, dengan alat dan akun Anda sendiri. Kami tidak pernah trading untuk Anda, tidak pernah menyimpan dana, dan tidak pernah meminta API key dengan izin trading.
Sebelum memulai
- Di halaman Alert, tambahkan webhook JSON Kustom. VIP Quant dan uji coba menyertakannya.
- Salin signing secret. Ini hanya ditampilkan sekali.
- Tekan Kirim tes dan pastikan endpoint Anda menerimanya.
- Arahkan sebuah aturan ke tujuan tersebut. Alert default juga dikirim ke sana selama aktif.
Balas dengan 2xx apa pun dalam 8 detik. Pengiriman yang gagal dicoba ulang dengan id yang sama, jadi lakukan deduplikasi berdasarkan itu. Setiap potongan kode di bawah telah diuji pada pengiriman bertanda tangan yang nyata. Anda bertanggung jawab atas apa pun yang Anda otomatiskan dari event ini.
Event
opportunity.alert: sebuah rute cocok dengan aturan atau alert default.rule.idbernilaidefaultuntuk alert default.listing.alert: sebuah pasar baru saja listing, dengan satu event per listing.opportunity.closed: rute yang pernah di-alert kepada Anda telah tertutup, jika aturan memintanya.position.alert: sesuatu terjadi pada trade yang dilacak.notice.alert: sebuah bursa mengumumkan delisting, penangguhan deposit atau penarikan, pemeliharaan, atau listing, pada Alert default.trackedmenandakan ini ada pada trade yang Anda lacak.watch.alert: wallet yang Anda pantau menerima atau mengirim transfer, pada Alert default.event.txbernilai null jika hash transaksinya tidak diketahui.alert.test: dari Kirim tes, dengan"test": truedan koin palsu bernama 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", "...": "..." }
}Field lengkapnya ada di dokumentasi webhook.
Periksa signature
Setiap request membawa X-ArbitrageX-Timestamp dan X-ArbitrageX-Signature, yaitu sha256= ditambah HMAC-SHA256 hex dari timestamp.rawBody. Periksa pada byte mentah, sebelum parsing, karena JSON yang di-parse dan di-encode ulang tidak akan cocok. Tolak timestamp yang selisihnya lebih dari 5 menit dari sekarang, untuk mencegah replay.
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")))
}Alat eksekusi
Setiap contoh di bawah adalah body yang diharapkan sebuah alat, ditulis berdasarkan payload standar kami: setiap nilai dalam kurung sudut adalah field kami yang harus diletakkan di sana. Buat di langkah Zapier atau Make setelah catch webhook, atau di relay Anda sendiri seperti penerima di Jalankan skrip Anda sendiri, lalu kirim ke URL webhook alat tersebut. Nilai dalam huruf kapital adalah milik Anda sendiri. Hanya opportunity.alert dan listing.alert yang boleh sampai ke alat: saring test dan setiap tipe lain, agar follow-up penutupan tidak bisa membuka trade. Kami tidak pernah trading: apa yang dilakukan alat dengan sebuah pesan adalah urusan Anda dan alat itu.
Setiap alat mengubah field-nya sendiri dari waktu ke waktu. Periksa contoh terhadap dokumentasi terbaru alat tersebut sebelum Anda mengandalkannya.
3Commas signal bot
Untuk webhook kustom signal bot 3Commas, dengan secret dan UUID bot dari pengaturan pesan bot. Signal bot trading di satu bursa, jadi batasi aturan ke rute yang membeli di sana dengan filter bursa pada aturan.
{
"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 menerima perintah melalui antarmukanya sendiri, bukan webhook, jadi ini dikirim ke script strategy kecil milik Anda, atau bridge ke antarmuka MQTT-nya. Gunakan nama connector Anda sendiri.
{
"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
Teks biasa dalam format sinyal Cornix, untuk alamat webhook yang diberikan Cornix untuk bot TradingView. Atur stop Anda sendiri, dan periksa formatnya terhadap panduan Cornix terbaru.
#<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
Nama field yang dikirim alert TradingView, untuk alat yang dibuat untuk menerima alert 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
Setiap angka di tingkat teratas, untuk spreadsheet, n8n, atau database.
{
"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>"
}Saluran lain
Setiap saluran di bawah disiapkan di halaman Alert, punya tombol Kirim tes, dan terbaca seperti pesan Telegram, dalam bahasa akun. Aturan, cooldown, jam senyap, batas laju, dan jeda otomatis bekerja sama di semuanya.
- Slack (Pro Desk ke atas): tambahkan aplikasi Incoming Webhooks ke sebuah channel di Slack dan tempel URL
hooks.slack.comyang diberikannya. - ntfy (Starter ke atas): notifikasi gratis di ponsel dan desktop. Tempel URL topik seperti
https://ntfy.sh/your-topic, atau satu di server Anda sendiri, dan berlangganan topik yang sama di aplikasi ntfy. Siapa pun yang tahu nama topik publik bisa membacanya, jadi pilih nama acak yang panjang, atau tambahkan access token untuk topik yang dilindungi. Setiap alert punya tombol untuk kedua bursa. - Ringkasan email (Starter ke atas): satu email ke alamat akun Anda berisi setiap alert sejak yang terakhir, setiap jam tepat pada jamnya atau setiap hari pukul 08:00 UTC. Tidak ada yang dikirim jika tidak ada yang baru.
WhatsApp tidak punya cara gratis bagi sebuah layanan untuk mengirimi Anda pesan, jadi tidak ditawarkan. Untuk hal lain, webhook kustom adalah JSON biasa melalui https.
Kirim ke Slack
Sebuah Cloudflare Worker tanpa server yang perlu dijalankan. Atur ARBITRAGEX_SECRET dan SLACK_WEBHOOK_URL sebagai secret, lalu gunakan URL Worker sebagai tujuan Anda. Kode yang sama berjalan di mana saja yang memiliki 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 });
},
};Simpan di spreadsheet
Menambahkan satu baris per alert ke CSV yang bisa Anda buka di spreadsheet apa pun, hanya dengan pustaka standar Python. Taruh di balik https: kami hanya mengirim ke alamat https publik.
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()Jalankan skrip Anda sendiri
Penerima tanpa dependensi, yang langsung membalas dan menyerahkan setiap alert ke satu fungsi milik Anda. Apa yang dilakukan fungsi itu adalah tanggung jawab Anda untuk menulis dan mempertanggungjawabkannya. Alert adalah estimasi dari data pasar, dan sebuah rute bisa tertutup sebelum apa pun yang Anda mulai tiba.
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 dan Make
Webhook kustom adalah POST JSON biasa, jadi URL catch webhook dari keduanya bisa dipakai sebagai tujuan. Tambahkan langkah filter pada type dan test, lalu petakan field seperti opportunity.coin dan opportunity.netBps. Langkah ini tidak memeriksa signature kami untuk Anda, jadi perlakukan URL catch seperti kata sandi dan jangan memulai apa pun di sana yang tidak akan Anda izinkan dimulai oleh orang asing.
Telegram
Jika Anda hanya ingin alert di ponsel, lewati semua ini. Tekan Hubungkan Telegram di halaman Alert lalu Start di chat yang terbuka. Lihat dokumentasi alert.