Receitas de webhook
Os alertas mostram o que o mercado está fazendo. Webhooks e Telegram são como você age a partir disso, com suas próprias ferramentas e suas próprias contas. Nunca operamos por você, nunca guardamos fundos e nunca pedimos chaves de API com permissão de negociação.
Antes de começar
- Na página Alertas, adicione um webhook JSON personalizado. O VIP Quant e o teste incluem esse recurso.
- Copie o segredo de assinatura. Ele é exibido uma única vez.
- Clique em Enviar teste e confira se seu endpoint o recebe.
- Aponte uma regra para o destino. O alerta padrão também vai para ele enquanto estiver ativo.
Responda com qualquer 2xx em até 8 segundos. Envios com falha são repetidos com o mesmo id, então remova duplicatas por ele. Cada trecho abaixo foi testado contra uma entrega assinada real. Você é responsável por tudo o que automatizar a partir desses eventos.
Eventos
opportunity.alert: uma rota atendeu a uma regra ou ao alerta padrão.rule.idédefaultpara o alerta padrão.listing.alert: um mercado acaba de ser listado, com um evento por listagem.opportunity.closed: uma rota sobre a qual você foi alertado fechou, se a regra pedir.position.alert: algo aconteceu numa operação acompanhada.notice.alert: uma corretora anunciou um delisting, uma suspensão de depósitos ou saques, uma manutenção ou uma listagem, no Alerta padrão.trackedindica que é uma operação que você acompanha.watch.alert: uma carteira que você monitora recebeu ou enviou uma transferência, no Alerta padrão.event.txé null quando o hash da transação não é conhecido.alert.test: vindo de Enviar teste, com"test": truee uma moeda fictícia chamada 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", "...": "..." }
}Os campos completos estão na documentação de webhooks.
Verifique a assinatura
Toda requisição traz X-ArbitrageX-Timestamp e X-ArbitrageX-Signature, que é sha256= mais o HMAC-SHA256 em hex de timestamp.rawBody. Verifique sobre os bytes brutos, antes de interpretar, pois um JSON interpretado e recodificado não vai bater. Recuse timestamps a mais de 5 minutos de agora, para impedir replays.
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")))
}Ferramentas de execução
Cada exemplo abaixo é o corpo que uma ferramenta espera, escrito sobre o nosso payload padrão: cada valor entre sinais de menor e maior é o nosso campo a colocar ali. Monte-o numa etapa do Zapier ou do Make após um webhook de captura, ou no seu próprio relay, como o receptor em Rodar seu próprio script, e envie-o para a URL de webhook da ferramenta. Valores em maiúsculas são seus. Só opportunity.alert e listing.alert devem chegar a uma ferramenta: filtre test e todos os outros tipos, para que um aviso de fechamento não abra uma operação. Nunca operamos: o que uma ferramenta faz com uma mensagem cabe a você e à ferramenta.
Cada ferramenta muda seus próprios campos com o tempo. Confira um exemplo com a documentação atual da ferramenta antes de confiar nele.
3Commas signal bot
Para o webhook personalizado de um signal bot da 3Commas, com o secret e o UUID do bot tirados das configurações de mensagem do bot. Um signal bot opera numa só corretora, então limite a regra às rotas que compram lá com o filtro de corretoras da regra.
{
"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
O Hummingbot recebe comandos pelas próprias interfaces, e não por webhook, então isto vai para uma pequena estratégia de script sua, ou para uma ponte até a interface MQTT dele. Use o nome do seu próprio conector.
{
"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
Texto simples no formato de sinal do Cornix, para o endereço de webhook que o Cornix dá a um bot do TradingView. Defina seu próprio stop e confira o formato com o guia atual do 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
Os nomes de campo que um alerta do TradingView envia, para ferramentas feitas para receber alertas do 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
Todos os números no nível superior, para uma planilha, o n8n ou um banco de dados.
{
"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>"
}Outros canais
Cada canal abaixo é configurado na página Alertas, tem um botão Enviar teste e é lido como a mensagem do Telegram, no idioma da conta. Regras, intervalos de espera, horário de silêncio, o limite de envios e a pausa automática funcionam igual em todos eles.
- Slack (Pro Desk e acima): adicione o app Incoming Webhooks a um canal no Slack e cole a URL
hooks.slack.comque ele fornecer. - ntfy (Starter e acima): notificações grátis no celular e no computador. Cole a URL de um tópico, como
https://ntfy.sh/your-topic, ou de um no seu próprio servidor, e assine o mesmo tópico no app ntfy. Qualquer pessoa que saiba o nome de um tópico público pode lê-lo, então escolha um nome longo e aleatório, ou adicione um token de acesso para um tópico protegido. Cada alerta tem botões para as duas corretoras. - Resumo por e-mail (Starter e acima): um e-mail para o endereço da sua conta com todos os alertas desde o último, a cada hora cheia ou todo dia às 08:00 UTC. Nada é enviado quando não há nada novo.
O WhatsApp não tem um meio gratuito para um serviço enviar mensagens a você, então não é oferecido. Para qualquer outra coisa, um webhook personalizado é JSON simples por https.
Publicar no Slack
Um Cloudflare Worker, sem servidor para manter. Defina ARBITRAGEX_SECRET e SLACK_WEBHOOK_URL como segredos e use a URL do Worker como destino. O mesmo código roda em qualquer lugar com 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 });
},
};Manter uma planilha
Adiciona uma linha por alerta a um CSV que você pode abrir em qualquer planilha, usando apenas a biblioteca padrão do Python. Coloque-o atrás de https: só enviamos para endereços https públicos.
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()Rodar seu próprio script
Um receptor sem dependências, que responde na hora e entrega cada alerta a uma função sua. O que essa função faz cabe a você escrever e a você responder. Os alertas são estimativas a partir de dados de mercado, e uma rota pode fechar antes que qualquer coisa que você iniciar chegue.
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 e Make
Um webhook personalizado é um POST JSON simples, então uma URL de recebimento de webhook de qualquer um dos dois funciona como destino. Adicione uma etapa de filtro em type e test e mapeie campos como opportunity.coin e opportunity.netBps. Essas etapas não verificam nossa assinatura para você, então trate a URL de recebimento como uma senha e não inicie nada ali que você não deixaria um estranho iniciar.
Telegram
Se você só quer alertas no celular, pule tudo isso. Clique em Conectar Telegram na página Alertas e depois em Iniciar no chat que abrir. Veja a documentação de alertas.