# POST /v2/tickers/{ticker}/subscribe

**Subscribe to a ticker**

Subscribes a single ticker. `condition` is a WHERE-clause fragment evaluated when this ticker is matched — you do not need to add `ticker = '…'` yourself; the endpoint scopes the predicate to the path ticker automatically. Plan-gated by webhook tier; counts against your account-wide webhook cap. Returns the same shape as `GET /v2/webhooks/{id}`, plus a `test_url` pointing at `POST /v2/webhooks/{id}/test` — subscribing does NOT auto-fire anything at your endpoint; hit the test URL to send a real-shape `webhook.fired` and validate your receiver.

Custom signals referenced in `condition` are expanded and frozen into the webhook at creation — editing the signal later will not change this subscription (re-subscribe to apply). See Create a custom signal.

## Plan access

- **Plan access.** Paid plans (Hobby and above).
- **Cadence.** Real-time on every paid plan — evaluated on every data refresh (~1×/min), so a match is delivered within seconds of it landing. Optionally throttle a webhook to hourly or at-market-open.
- **Capacity.** Hobby 10 · Pro 100 · Scale & Enterprise unlimited.

## Body parameters

| Name | In | Type | Required | Description |
|------|----|----|----------|-------------|
| `ticker` | path | string | yes | Ticker symbol (case-insensitive). Example: `NVDA`. |
| `condition` | body | string | yes | WHERE-clause fragment using signal/column names from the schema. Example: `rsi_oversold AND high_volume_alert`. |
| `target_url` | body | string | no | https:// URL to POST when the condition fires. Omit for in-app delivery (visible in the dashboard). |
| `channel` | body | string | no | 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. Enum: `webhook`, `discord`, `in_app`, `mobile_push`. |
| `discord_url` | body | string | no | Discord incoming-webhook URL (`https://discord.com/api/webhooks/…`). Required when `channel` is `discord`. Stored as a posting credential and stripped from list/get responses, which set `channel_config_present: true` instead. |
| `cadence` | body | string | no | How often to evaluate. `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`. Enum: `realtime`, `hourly`, `nyse_open`. |
| `name` | body | string | no | Human-readable label (up to 80 chars). Defaults to `<TICKER>: <condition>`. |
| `fields` | body | string | no | 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. Example: `rsi_14,macd_line`. |

## Status codes

- **200** — Webhook subscription created.
- **400** — `bad_request` — missing `condition`, invalid ticker, or malformed `target_url`.
- **403** — `webhook_tier_required` or `webhook_limit_reached`.
- **404** — `not_found` — ticker is not tracked.

## Sample response

```json
{
  "id": "wh_smRsF3-z36o",
  "name": "NVDA: rsi_oversold AND high_volume_alert",
  "q": "ticker = 'NVDA' AND (rsi_oversold AND high_volume_alert)",
  "cadence": "realtime",
  "target_url": "https://example.com/hook",
  "delivery": "webhook",
  "status": "active",
  "subscription_origin": { "type": "ticker", "ref": "NVDA", "condition": "rsi_oversold AND high_volume_alert" },
  "signing_secret": "whsec_…",
  "test_url": "/v2/webhooks/wh_smRsF3-z36o/test",
  "created_at": 1737580800
}
```

## Examples

### Subscribe NVDA on RSI + volume

Request:

```shell
curl -X POST "https://api.tickerbot.io/v2/tickers/NVDA/subscribe" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"condition":"rsi_oversold AND high_volume_alert","target_url":"https://example.com/hook","cadence":"realtime"}'
```

Response (`200`):

```json
{
  "id": "wh_smRsF3-z36o",
  "name": "NVDA: rsi_oversold AND high_volume_alert",
  "q": "ticker = 'NVDA' AND (rsi_oversold AND high_volume_alert)",
  "cadence": "realtime",
  "target_url": "https://example.com/hook",
  "delivery": "webhook",
  "status": "active",
  "subscription_origin": { "type": "ticker", "ref": "NVDA", "condition": "rsi_oversold AND high_volume_alert" },
  "signing_secret": "whsec_…",
  "test_url": "/v2/webhooks/wh_smRsF3-z36o/test",
  "created_at": 1737580800
}
```

### Deliver to Discord instead of an HTTPS endpoint

Request:

```shell
curl -X POST "https://api.tickerbot.io/v2/tickers/NVDA/subscribe" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"condition":"rsi_oversold AND high_volume_alert","channel":"discord","discord_url":"https://discord.com/api/webhooks/123456789012345678/aBcDeF…","cadence":"realtime"}'
```

Response (`200`):

```json
{
  "id": "wh_dC9rTq-k77p",
  "name": "NVDA: rsi_oversold AND high_volume_alert",
  "q": "ticker = 'NVDA' AND (rsi_oversold AND high_volume_alert)",
  "cadence": "realtime",
  "target_url": null,
  "channel": "discord",
  "delivery": "discord",
  "channel_config_present": true,
  "status": "active",
  "subscription_origin": { "type": "ticker", "ref": "NVDA", "condition": "rsi_oversold AND high_volume_alert" },
  "test_url": "/v2/webhooks/wh_dC9rTq-k77p/test",
  "created_at": 1737580800
}
```

---

Interactive sandbox + parameter editor: https://tickerbot.io/docs/endpoints/tickers/subscribe
