Webhooks deliver your wallet's account events to your server as soon as BotRadix indexes them from the chain, so you do not need to poll aggressively.
Creating a webhook#
Webhooks are managed with a developer session (see API overview and authentication):
curl -X POST https://app.botradix.com/api/dex/developer/webhooks \
-H "Authorization: Bearer $DEVELOPER_SESSION" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/botradix/webhook","events":["order.filled","position.changed","liquidation"]}'| Rule | Value |
|---|---|
| URL | HTTPS on port 443 or 8443, no embedded credentials, up to 512 characters, resolving to public IP addresses |
| Limit | Up to 8 active webhooks per wallet |
| Events | A list of event names, or ["*"] for all. Default: transaction.confirmed and transaction.reverted |
| Secret | signing_secret (starts with brx_whsec_) is returned only once, when the webhook is created |
| Removal | DELETE /api/dex/developer/webhooks/{webhook_id} disables the subscription |
Events#
| Event | When |
|---|---|
order.accepted | An order was accepted on chain |
order.filled | An order was completely filled |
order.partially_filled | An order was partly filled |
order.cancelled | An order was cancelled |
position.changed | A position was opened, increased, reduced, closed or inverted |
liquidation | A position was liquidated |
funding.charged / funding.credited | Funding was paid or received |
collateral.changed | A deposit or withdrawal changed your collateral |
transaction.confirmed / transaction.reverted | A transaction BotRadix knows about reached finality, successfully or not |
chain.reorg | A chain reorganization changed already reported data |
transaction.pending | Sent only as a correction after a reorganization (with revoked_event_id) |
liquidation.warning is accepted in subscriptions but reserved: it is not emitted today.
Note
transaction.* events are sent for transactions BotRadix knows the hash of: those submitted through the relay or the delegated executor, and those whose hash you reported to /api/dex/actions/receipt.
Delivery#
Each delivery is an HTTPS POST with a JSON body:
{ "id": "…", "type": "position.changed", "created_at": 1790950000, "data": { … } }| Header | Content |
|---|---|
X-BotRadix-Event | Event type |
X-BotRadix-Event-ID | Unique, stable event ID (up to 96 characters, same as id in the body). Use it to deduplicate |
X-BotRadix-Timestamp | Unix time (seconds) of this delivery attempt |
X-BotRadix-Signature | v1= followed by the hex HMAC-SHA256 signature |
User-Agent | BotRadix-DEX-Webhook/1.0 |
- Delivery is at least once: the same event can arrive more than once. Deduplicate by
X-BotRadix-Event-ID. - Answer with any
2xxstatus within 4 seconds. Redirects are not followed. - A failed delivery is retried up to 4 attempts in total, waiting 2, 4 and 8 seconds.
- Events can arrive out of order; use
created_atand the data itself to order them.
Verifying the signature#
The signature is an HMAC-SHA256 of timestamp + "." + raw_body, keyed with your full signing_secret. Always verify it against the raw body, before parsing the JSON, and reject old timestamps.
// Node.js (Express: use express.raw({ type: "application/json" }) for this route)
import crypto from "node:crypto";
export function verifyBotRadixWebhook(rawBody, headers, secret, toleranceSeconds = 300) {
const timestamp = headers["x-botradix-timestamp"];
const received = headers["x-botradix-signature"] || "";
if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false;
const expected = "v1=" + crypto.createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest("hex");
return received.length === expected.length && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
}# Python
import hashlib, hmac, time
def verify_botradix_webhook(raw_body: bytes, headers, secret: str, tolerance: int = 300) -> bool:
timestamp = headers.get("X-BotRadix-Timestamp", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance:
return False
expected = "v1=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(headers.get("X-BotRadix-Signature", ""), expected)Server-Sent Events stream#
GET /api/dex/v1/stream?pair_id={id}&interval=1m is defined as a private stream with market snapshots, account snapshots and lifecycle events, a heartbeat every 12 seconds and resumption with Last-Event-ID. It is not enabled on the public service yet and answers 503 REALTIME_STREAM_ASYNC_WORKER_REQUIRED. Use webhooks for account events and the read endpoints for market data in the meantime. This page will be updated when the stream is enabled.