# POST /v2/webhooks

**Create a subscription**

Register a SQL WHERE clause (inline `q` or `rule_id`) + an HTTPS target URL. On the cadence configured for your plan, we evaluate the clause against the relevant ticker universe. When a new ticker enters the match set, we POST a `webhook.fired` event to your target. On creation we also send a one-shot `webhook.ping` to verify reachability; the subscription stays in `pending_verification` until the ping returns a 2xx response, then flips to `active`.

## Plan access

- **Plan access.** Included on every plan.
- **Cadence.** Hobby 5m · Pro 1m · Scale 1m · Enterprise 1m.
- **Capacity.** Hobby 5 · Pro 25 · Scale 100 · Enterprise unlimited.

## Body parameters

| Name | In | Type | Required | Description |
|------|----|----|----------|-------------|
| `name` | body | string | yes | Human-readable label. Helps you find this webhook later in `GET /v2/webhooks`. Doesn't need to be unique. Example: `breakouts-on-tech`. |
| `q` | body | string | no | SQL WHERE clause. Same grammar as `/v2/scan`. Either this or `rule_id` is required. Example: `breakout AND sector = 'Technology'`. |
| `rule_id` | body | string | no | Slug of a saved rule (see `/v2/rules`). The webhook stores a snapshot of the rule's `q` + `universe_id` at creation time. |
| `target_url` | body | string | yes | HTTPS endpoint that will receive POSTed events. Must start with `https://`. Plain HTTP is rejected. Example: `https://your-app.example.com/webhooks/tickerbot`. |
| `cadence` | body | string | no | How often to evaluate this webhook. Defaults to your plan's fastest cadence; pick a slower one to conserve quota or smooth out noisy fires. Enum: `1m`, `5m`, `15m`, `hourly`, `nyse_open`. |

## Status codes

- **201** — Webhook created. The response is the full doc, including the one-time `signing_secret` you'll use to verify deliveries. Save it; it's never returned again.
- **400** — Missing parameter, oversized parameter, non-HTTPS URL, or both `q` and `rule_id` supplied.
- **401** — Missing or invalid API key.
- **402** — Query references a premium signal and your plan does not include premium signals.
- **403** — `webhook_tier_required` (free), `webhook_limit_reached` (over plan cap), or `cadence_above_plan_max`.
- **404** — `rule_not_found` when the referenced `rule_id` does not exist.

## Sample response

```json
{
  "as_of": "2026-05-14T11:41:01.000Z",
  "id": "wh_smRsF3-z36o",
  "userId": "0HPv1WJKzROlLQzfhzodE6oquzx1",
  "keyId": "496ee88f-5851-4c88-9703-937d3d35ee85",
  "name": "breakouts-on-tech",
  "q": "breakout AND sector = 'Technology'",
  "target_url": "https://your-app.example.com/webhooks/tickerbot",
  "signing_secret": "whsec_jqu0eq7nCK4gSz2vdzZAHQmm6y1hCbWj",
  "status": "pending_verification",
  "source": "v2",
  "created_at": 1778720416,
  "updated_at": 1778720416,
  "last_match_set": [],
  "last_evaluated_at": null
}
```

## Examples

### Create

Request:

```shell
curl -X POST https://api.tickerbot.io/v2/webhooks \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"breakouts-on-tech","q":"breakout AND sector = '\''Technology'\''","target_url":"https://your-app.example.com/webhooks/tickerbot"}'
```

Response (`201`):

```json
{
  "as_of": "2026-05-14T11:41:01.000Z",
  "id": "wh_smRsF3-z36o",
  "userId": "0HPv1WJKzROlLQzfhzodE6oquzx1",
  "keyId": "496ee88f-5851-4c88-9703-937d3d35ee85",
  "name": "breakouts-on-tech",
  "q": "breakout AND sector = 'Technology'",
  "target_url": "https://your-app.example.com/webhooks/tickerbot",
  "signing_secret": "whsec_jqu0eq7nCK4gSz2vdzZAHQmm6y1hCbWj",
  "status": "pending_verification",
  "source": "v2",
  "created_at": 1778720416,
  "updated_at": 1778720416,
  "last_match_set": [],
  "last_evaluated_at": null
}
```

## Notes

- Per-plan caps: Hobby 5 · Pro 25 · Scale 100 · Enterprise unlimited.
- Timestamps (`created_at`, `updated_at`, etc.) are unix-seconds integers. `as_of` at the top level is ISO.

---

Interactive sandbox + parameter editor: https://tickerbot.io/api/endpoints/webhooks/create
