Alerts & webhooks

Let the engine do the watching: create price-cross and percent-move alerts over the API, and receive an HTTP call to your own server the moment one fires, evaluated on every tick, 24/7.

GET/v1/alerts
POST/v1/alerts
DELETE/v1/alerts/{id}

Polling for a condition is the most wasteful pattern in trading automation: thousands of requests to observe that nothing happened yet. Alerts invert it. You register the condition once; our alert engine (the same one behind the site's alerts) evaluates it against every live tick and, at the moment of truth, notifies every channel your account has configured plus, for API-created alerts, POSTs a JSON payload to your webhook. Your side spends zero requests while waiting.

Alert types

TypeConditionDirectionsExtra fields
priceLive price reaches a levelabove / belowprice: the level. Must be on the far side of the current price at creation (validated, else 422).
pctThe current candle moves at least X percentup / down / anypct (0.1-500) and window (5m 15m 1h 4h 1d 1w): the candle timeframe measured open→now. Needs a plan that includes % move alerts (422 otherwise).

Because a price alert's level must start on the far side of the market, "reach" and "cross" are the same event; an alert can never fire at the creation instant. One-shot alerts flip to triggered status after firing; repeat: true alerts stay active and re-arm with hysteresis (price must retreat ~0.1% past the level before it can fire again, so an oscillation exactly on the line will not spam you). Percent alerts fire at most once per candle bucket. Repeating alerts are plan-gated; active alerts count against your plan's price alert cap, a separate pool from the market and density alert caps.

Creating an alert

POST /v1/alerts: a price alert with a webhook
curl -X POST "https://osiristrader.com/api/v1/alerts" \
  -H "Authorization: Bearer osk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "BTCUSDT",
    "type": "price",
    "direction": "below",
    "price": 60000,
    "note": "weekly support retest",
    "webhook_url": "https://your.app/hooks/osiris?secret=d41d8cd98f"
  }'
{
  "data": {
    "id": 91, "symbol": "BTCUSDT", "type": "price", "direction": "below", "price": 60000,
    "pct": null, "window": null, "repeat": false, "note": "weekly support retest",
    "webhookUrl": "https://your.app/hooks/osiris?secret=d41d8cd98f",
    "status": "active", "createdPrice": 63516.6, "lastTriggeredAt": null,
    "triggerCount": 0, "createdAt": "2026-08-14T09:12:00+00:00"
  }
}
A repeating percent-move alert that fires on every ±3% 15m candle
curl -X POST "https://osiristrader.com/api/v1/alerts" \
  -H "Authorization: Bearer osk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "symbol": "SOLUSDT", "type": "pct", "direction": "any",
        "pct": 3, "window": "15m", "repeat": true,
        "webhook_url": "https://your.app/hooks/osiris?secret=d41d8cd98f" }'

Listing & deleting

# active (default), or ?status=triggered / ?status=all
curl "https://osiristrader.com/api/v1/alerts?status=all" \
  -H "Authorization: Bearer osk_YOUR_KEY"

curl -X DELETE "https://osiristrader.com/api/v1/alerts/91" \
  -H "Authorization: Bearer osk_YOUR_KEY"

The API and the site's alerts page manage the same records: alerts created here appear in Settings → Alerts and vice versa. (The webhook_url field is API-only; UI-created alerts simply have none.) This page covers price alerts only: market alerts (Fear & Greed, board numbers) and density alerts (order-book wall watches) are managed on the site, each with its own plan cap, and have no API endpoints yet.

Alerts are venue-bound

Every alert watches one exchange's price feed, fixed at creation. Pass exchange (binance, bybit, okx, bitget) in the create payload to arm the alert against that venue's market; omitted, it binds to the default venue where the pair is listed. The bound venue comes back as the exchange field on every alert row. The same pair on two venues is two different markets; set two alerts if you watch both. If the venue later delists the pair, the alert is removed and you are notified.

Webhook delivery

When an alert with a webhook_url fires, we POST once, from our servers, with a 10-second timeout. Non-2xx responses and timeouts are retried up to 3 attempts total (10 s, then 60 s after the first failure). Webhook delivery runs alongside your other notification channels, never instead of them.

The delivery (header X-Osiris-Event: alert.triggered)
POST /hooks/osiris?secret=d41d8cd98f HTTP/1.1
Content-Type: application/json
X-Osiris-Event: alert.triggered

{
  "event": "alert.triggered",
  "alert": {
    "id": 91, "exchange": "binance", "symbol": "BTCUSDT", "type": "price", "direction": "below",
    "price": 60000, "pct": null, "window": null, "repeat": false,
    "note": "weekly support retest"
  },
  "trigger": { "price": 59987.2, "move": null, "time": 1783877000000 }
}
FieldDescription
alertThe alert as configured, with everything needed to route the event without a lookup.
trigger.priceThe price at the firing tick.
trigger.moveFor pct alerts, the signed percent move that fired it; null for price alerts.
trigger.timeFiring timestamp, ms.

Webhook endpoint requirements & best practices

  • Public URL, http(s), max 500 chars. Targets must resolve to a public address; localhost and private ranges are rejected at creation with 422. For local development, use a tunnel (cloudflared, ngrok).
  • Authenticate deliveries with a secret in your URL. Include a random token as a query parameter or path segment and verify it on every request; anyone who knows your URL shape could POST fake events otherwise. Treat the full URL as a credential.
  • Respond fast, process async. Return 200 immediately and hand the payload to a queue. A handler that trades synchronously inside the webhook request risks eating into the 10-second timeout and triggering a retry mid-action.
  • Be idempotent. Retries mean at-least-once delivery. Deduplicate on (alert.id, trigger.time); that pair is unique per firing.
  • Design for the miss. If all 3 attempts fail (your endpoint was down), that firing is not re-delivered. On recovery, reconcile with GET /v1/alerts?status=all: lastTriggeredAt and triggerCount tell you exactly what you missed.
The pattern that ties the API together: pull structure from Levels, set price alerts on the zones that matter, receive the webhook, then act on it, with a Metrics read for confirmation. The engine watches 500 pairs so your code only wakes for the moments that count.