View as markdown
Endpoints · Signals

Subscribe to a signal

POSThttps://api.tickerbot.io/v2/signals/{signal}/subscribe

Push one signal: we POST your endpoint whenever any ticker starts matching it.

stringrequired

Signal name from the schema (case-insensitive).

string

Required for numeric signals. Shape: ">70", "<30", ">=100", "=50". Sending one with a boolean flag or custom signal returns 400 (it does not apply).

string

Restrict to a single ticker. Default: any ticker.

string

Restrict to a system or user-owned universe (e.g. top_100). universe_id accepted as an alias. Unknown universes are a 404 universe_not_found.

string

https:// URL to POST when fired. Omit for in-app delivery.

enum

Delivery channel. webhook (POST to target_url), discord (post an embed to discord_url), in_app (dashboard only), or mobile_push (notify a phone signed in to the Tickerbot mobile app; requires a device_id from POST /v2/devices/register). Inferred when omitted: webhook if target_url is set, discord if discord_url is set, else in_app. slack is reserved and returns 501. See the Delivery channels guide.

webhookPOST the payload to an HTTPS endpoint you own, signed with X-Tickerbot-Signature.
discordPost to a Discord channel via its webhook URL. Delivered as a Discord embed, never HMAC-signed — the URL is itself the credential.
in_appDeliver to the in-app feed. No external receiver, so nothing to validate and no test_url.
mobile_pushPush to a registered device. Requires a device_id from Register a device.
string

Discord incoming-webhook URL (https://discord.com/api/webhooks/…). Required when channel is discord. Stored as a posting credential: the create response echoes it back under channel_config, but every later read (list, get, deliveries) strips it and sets channel_config_present: true instead.

string

Device to notify, from POST /v2/devices/register. Required when channel is mobile_push; unknown ids are a 404 device_not_found.

enum

realtime (the default) is evaluated on every data refresh (~1×/min); hourly and nyse_open throttle to a batch schedule. 1m is a deprecated alias for realtime.

realtimeEvaluate on every refresh — the canonical value. (1m is a deprecated alias that collapses to this.)
hourlyBatch schedule: evaluate once an hour.
nyse_openBatch schedule: evaluate once per session, at the NYSE open.
string

Human-readable label (up to 80 chars). Defaults to the predicate — at_52w_high for a boolean, rsi_14 > 70 for a numeric, prefixed with <TICKER>: when ticker scopes it.

string[]

Comma-separated extra columns to include in each fired payload match row, beyond the standard set (ticker, name, asset_type, price, day_change_pct, market_cap). Each must be a real column; an unknown column is rejected at creation. fields accepted as an alias — and note the RESPONSE reports them under fields, as an array.

stringdefault market_cap

Column the fired payload's match lists are sorted by before the 100-row cap is applied, so a truncated list is the deterministic top 100 rather than an arbitrary sample. Must be a real column (validated at creation).

enumdefault desc

Sort direction for order.

ascAscending — smallest or earliest first.
descdefaultDescending — largest or most recent first.
string

Optional unique string (≤255 chars). A retry carrying the same key within 24h replays the original response (Idempotency-Replayed: true header) instead of creating a duplicate; a concurrent duplicate gets 409 idempotency_in_flight; reusing a key for a *different* request (another method or path) gets 422 idempotency_key_reused. 5xx responses are not stored — those retries re-execute.

201
Webhook subscription created — the same shape as GET /v2/webhooks/{id}, plus test_url and, uniquely on create, signing_secret (shown once, for HMAC verification) and the raw channel_config you supplied. Later reads strip both.
400
bad_request — invalid signal name, missing condition on a numeric signal, malformed condition, etc.
404
409
idempotency_in_flight — a concurrent request carried the same Idempotency-Key.
422
idempotency_key_reused — the Idempotency-Key was already used for a different request.
501
channel_not_implementedchannel: "slack" is reserved.
  • Shorthand for POST /v2/webhooks with a { type: "signal" } trigger — one of the four doors to the same webhook object, listed, updated, and deleted at /v2/webhooks.
  • For boolean signals (at_52w_high, golden_cross) the predicate is signal = true, and sending a condition returns 400 — it does not apply, and used to be silently ignored. Numeric signals (rsi_14, change_1m) require condition, shaped ">70", "<30", ">=100", "=50". Scope to a single ticker or a universe if you want less than the whole market.
  • Wire-delivered subscriptions (webhook, discord) come back with a test_url. Subscribing does NOT auto-fire — hit it to send a real-shape webhook.fired and validate your receiver. In-app subscriptions have no external receiver and carry no test_url.
  • {signal} may be one of your custom signals, resolved as a boolean. The subscription freezes that signal's SQL at creation, so editing the signal later does not change it — re-subscribe to apply.
  • subscription_origin.condition echoes the predicate in display form: the bare signal name for a boolean or custom signal (there is no bound to show), and <signal> <op> <value> for a numeric. It is not the condition you sent — booleans reject one outright.
  • No target_url (and no discord_url/device_id) means in-app delivery: channel and delivery come back in_app, and there is no test_url, since there is no external receiver to validate.
Numeric signal — RSI above 70 on top_100
curl -X POST "https://api.tickerbot.io/v2/signals/rsi_14/subscribe" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"condition":">70","universe":"top_100","cadence":"realtime"}'
Response
{
  "as_of": "2026-07-27T16:43:00.000Z",
  "id": "wh_aB3cD9-x14m",
  "name": "rsi_14 > 70",
  "q": "rsi_14 > 70",
  "rule_id": null,
  "fields": null,
  "order": null,
  "dir": null,
  "universe_id": "top_100",
  "cadence": "realtime",
  "channel": "in_app",
  "target_url": null,
  "delivery": "in_app",
  "signing_secret": "whsec_…",
  "status": "active",
  "source": "v2",
  "subscription_origin": {
    "type": "signal",
    "ref": "rsi_14",
    "condition": "rsi_14 > 70"
  },
  "created_at": 1785170580,
  "updated_at": 1785170580,
  "...": "(evaluator bookkeeping fields)"
}