Webhook 範例
警示告訴您市場正在發生什麼。Webhook 與 Telegram 則讓您以自己的工具和帳戶採取行動。我們絕不替您交易、絕不保管資金,也絕不要求具交易權限的 API 金鑰。
開始之前
- 在「警示」頁面新增自訂 JSON Webhook。VIP Quant 與試用包含此功能。
- 複製簽章密鑰,它只會顯示一次。
- 按「傳送測試」,確認您的端點有收到。
- 將規則指向此接收端。預設警示開啟時也會傳送至此。
請在 8 秒內回應任何 2xx。傳送失敗會以相同的 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", "...": "..." }
}完整欄位請見 Webhook 文件。
檢查簽章
每個請求都帶有 X-ArbitrageX-Timestamp 與 X-ArbitrageX-Signature,其值為 sha256= 加上 timestamp.rawBody 的十六進位 HMAC-SHA256。請在解析前以原始位元組檢查,因為解析後重新編碼的 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")))
}執行工具
以下每個範例都是工具預期的內容,依我們的標準內容格式撰寫:角括號中的每個值,就是要放在該處的我方欄位。可在 Zapier 或 Make 中 catch webhook 之後的步驟建立,或在您自己的中繼程式中建立,例如「執行您自己的腳本」中的接收器,再發送到工具的 Webhook URL。大寫的值由您自行填入。只有 opportunity.alert 與 listing.alert 應該送到工具:請過濾掉 test 及其他所有類型,以免關閉的後續通知開啟交易。我們絕不交易:工具如何處理訊息,由您與該工具決定。
每個工具都會隨時間變更自己的欄位。在依賴範例之前,請對照工具目前的文件檢查。
3Commas signal bot
適用於 3Commas 訊號機器人的自訂 Webhook,其密鑰與機器人 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 透過自己的介面而非 Webhook 接收指令,因此這會送到您自己的小型腳本策略,或連接其 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 機器人的 Webhook 位址。請自行設定停損,並對照 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 以上方案):在 Slack 中將 Incoming Webhooks 應用程式加入頻道,並貼上它提供的
hooks.slack.comURL。 - ntfy(Starter 以上方案):手機與桌面的免費通知。貼上主題 URL,例如
https://ntfy.sh/your-topic,或您自己伺服器上的主題,並在 ntfy 應用程式中訂閱同一主題。任何知道公開主題名稱的人都能讀取它,因此請選擇一個又長又隨機的名稱,或為受保護的主題加上存取權杖。每則警示都有兩家交易所的按鈕。 - 電子郵件摘要(Starter 以上方案):寄一封郵件到您帳戶的地址,內含自上次以來的每則警示,可選每小時整點或每天 08:00 UTC。沒有新內容時不會寄送。
WhatsApp 沒有讓服務免費傳訊給您的方式,因此不提供。其他任何工具都可使用自訂 Webhook,它是透過 https 傳送的純 JSON。
發送至 Slack
使用 Cloudflare Worker,無需自行架設伺服器。將 ARBITRAGEX_SECRET 與 SLACK_WEBHOOK_URL 設為密鑰,再以 Worker 的 URL 作為接收端。相同程式碼可在任何支援 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 });
},
};記錄到試算表
僅使用 Python 標準函式庫,每則警示在 CSV 中新增一行,可用任何試算表開啟。請置於 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
自訂 Webhook 是純 JSON POST,因此兩者的 catch webhook URL 都可作為接收端。在 type 與 test 上新增篩選步驟,再對應 opportunity.coin 與 opportunity.netBps 等欄位。這些步驟不會替您檢查我們的簽章,因此請將 catch URL 視為密碼,不要在那裡啟動任何您不願讓陌生人啟動的動作。
Telegram
如果您只想在手機上收到警示,可以跳過以上所有內容。在「警示」頁面按「連接 Telegram」,然後在開啟的對話中按 Start。請見警示文件。