DOJO Signal Feed API — Developer Guide
Version 1 · Your base URL comes with your API key. The examples below call it
$DOJO_API.
DOJO watches the markets you subscribe to and POSTs a signed JSON signal to your webhook whenever its analysis engine detects a trade setup — entry, stop-loss, and take-profit levels included. You bring a ticker list, a timeframe, and an HTTPS endpoint; DOJO does the rest.
1. Authentication
Every /v1/* request carries your API key as a Bearer token:
Authorization: Bearer <your key>
Keys are issued by the DOJO team and never recoverable — store them in a
secret manager. 401 means the key is missing, malformed, or unknown;
403 means the account is suspended.
2. Quick start
curl -X POST $DOJO_API/v1/subscriptions \
-H "Authorization: Bearer $DOJO_KEY" \
-H "Content-Type: application/json" \
-d '{
"tickers": ["FOREXCOM:XAUUSD", "BINANCE:BTCUSDT"],
"timeframe": "5",
"webhook_url": "https://your-app.example.com/hooks/dojo",
"webhook_secret": "a-strong-random-string"
}'
Response — 202 Accepted (provisioning is asynchronous):
{
"subscription_id": "sub_7f3a1c9e2b40d581",
"status": "provisioning",
"timeframe": "5",
"tickers": {
"FOREXCOM:XAUUSD": "provisioning",
"BINANCE:BTCUSDT": "provisioning"
}
}
Poll GET /v1/subscriptions/{id} until status is active — typically
60–120 seconds per new ticker. From then on, signals arrive at your
webhook.
3. Endpoints
POST /v1/subscriptions
Create a subscription.
| Field | Type | Rules |
|---|---|---|
tickers |
string[] | 1–20 per subscription. Format EXCHANGE:SYMBOL, uppercase (e.g. NASDAQ:AAPL). Unknown tickers are rejected per-ticker, not whole-request. |
timeframe |
string | One of 1, 5, 15, 60, 240 (minutes) or D (daily). One timeframe per subscription. |
webhook_url |
string | HTTPS only. Must resolve to a public address — private, loopback, and link-local hosts are rejected (webhook_host_not_public). |
webhook_secret |
string | Optional. When set, every delivery is HMAC-signed (see §5). Strongly recommended. |
Whole-request failures return 422 with a detail string. Capacity
limits return 429: max 10 live subscriptions per client, and a global
resource ceiling (subscribe with fewer tickers or retry later).
Per-ticker statuses you may see: provisioning, active,
failed:unknown_ticker, failed:malformed, failed:not_uppercase,
failed:missing_exchange_prefix.
GET /v1/subscriptions/{id}
Current state. status is one of:
| Status | Meaning |
|---|---|
provisioning |
Setup in progress — no signals yet |
active |
Live — signals flow to your webhook |
paused |
Deliveries suspended after repeated webhook failures (§6) |
cancelled |
Terminal — create a new subscription to resume coverage |
GET /v1/subscriptions/{id}/deliveries
Your delivery log, newest first (last 50). Each entry:
{
"delivery_id": "dlv_e7190fc236fdffbd",
"signal_id": "d728de64c1a4f2e8",
"status": "delivered",
"attempt": 1,
"http_status": 200,
"last_error": null,
"payload": { "…the exact body we POSTed…": "…" },
"delivered_at": "2026-09-01T20:47:28Z",
"next_retry_at": null,
"dead_at": null,
"created_at": "2026-09-01T20:47:26Z"
}
status is delivered, pending (retrying), or dead (gave up — §6).
POST /v1/subscriptions/{id}/resume
Re-activate a paused subscription after you have fixed your endpoint.
Returns 200 {"status": "active"}, or 404 if the subscription is not
paused. Dead deliveries are history — they are not re-sent.
DELETE /v1/subscriptions/{id}
Cancel. Returns 200 {"status": "cancelled"}. Idempotent-safe: a second
call returns 404.
GET /v1/status
Service health plus your own usage:
{
"status": "operational",
"degraded_reasons": [],
"box": { "last_worker_poll_s_ago": 10.8 },
"resources": { "active": 12, "capacity": 100 },
"alerts": { "used": 24, "engine_reserve": 20, "budget": 400 },
"deliveries_24h": { "delivered": 41, "dead": 0, "pending": 1 },
"subscriptions": { "total": 2, "active": 2, "paused": 0 }
}
degraded means signal detection or delivery may be delayed; already-
detected signals are never lost, they deliver late.
4. The signal webhook
When a setup is detected, DOJO POSTs JSON to your webhook_url:
{
"event": "signal",
"signal_id": "d728de64c1a4f2e8",
"subscription_id": "sub_7f3a1c9e2b40d581",
"ticker": "FOREXCOM:XAUUSD",
"timeframe": "5",
"side": "buy",
"entry": 4428.05,
"sl": 4425.36,
"tps": [4430.47, 4432.61, 4434.17],
"bar_time": "2026-09-01T05:15:00Z",
"detected_at": "2026-09-01T05:15:04Z"
}
sideisbuyorsell;entry/sl/tpsare price levels in the instrument's chart units, exactly as analyzed — no scaling, no rounding.tpsis ordered nearest-first and may hold 1 or more targets.tickerechoes the form you subscribed with.bar_timeis the close of the bar that produced the setup;detected_atis when our engine confirmed it. Expected end-to-end latency from bar close to your webhook: ~3–10 seconds.
Headers on every delivery:
| Header | Value |
|---|---|
Content-Type |
application/json |
User-Agent |
dojo-delivery/1.0 |
X-Dojo-Delivery |
Unique id per delivery attempt |
X-Dojo-Signature |
sha256=<hex> — only when your subscription has a webhook_secret (§5) |
Respond with any 2xx within 10 seconds. Do your processing async —
acknowledge first, act after.
5. Verifying signatures
The signature is an HMAC-SHA256 of the raw request body with your
webhook_secret. Always compare with a constant-time function.
Python:
import hashlib, hmac
def verify(raw_body: bytes, header_value: str, secret: str) -> bool:
expect = "sha256=" + hmac.new(secret.encode(), raw_body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(header_value, expect)
Node:
const crypto = require("crypto");
function verify(rawBody, headerValue, secret) {
const expect = "sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(headerValue),
Buffer.from(expect));
}
Verify against the raw bytes — re-serializing the parsed JSON will change key order and break the digest.
6. Delivery semantics — read this twice
- At-least-once. The same signal can reach you more than once (a retry
after a timeout you actually served, for example). Dedupe on
signal_id+subscription_id— that pair is stable across retries;X-Dojo-Deliverychanges per attempt. - Retries. A non-2xx response or a timeout is retried up to 5 attempts with backoff: 10s → 60s → 5min → 15min.
- Dead-letter + auto-pause. After the 5th failure the delivery is
marked
deadand your subscription flips topaused— we stop calling a broken endpoint. Fix your side, thenPOST …/resume. Signals that fire while paused are not queued. - Ordering is not guaranteed under retry. Use
bar_timeto sequence.
7. Error reference
| Code | Where | Meaning |
|---|---|---|
401 |
any /v1/* |
Missing/malformed/unknown API key |
403 |
any /v1/* |
Client suspended |
404 |
subscription routes | Not yours, unknown, or wrong state (e.g. resume on a non-paused sub) |
422 |
POST /v1/subscriptions |
Whole-request validation failure — detail says which field |
429 |
POST /v1/subscriptions |
Subscription or capacity limit reached |
Per-ticker problems never fail the request — check the tickers map.
8. Limits (v1)
| Limit | Value |
|---|---|
| Tickers per subscription | 20 |
| Live subscriptions per client | 10 |
| Timeframes | 1, 5, 15, 60, 240, D |
| Delivery attempts | 5, then dead-letter |
| Webhook response window | 10 s |
Questions or a key request: sensei@clawback.capital.