Endpoints

Webhooks

A webhook is a saved SQL WHERE clause with a delivery target. Once a minute, we evaluate every webhook against the live universe and POST one event to each whose match set just gained new tickers. This is the API analog of the mobile product's alerts.

The pattern, end-to-end

Three steps. Step one is the only API call you make.

  1. Create the webhook with POST /v1/webhooks. Provide a name, the SQL query, and an HTTPS URL we’ll deliver to.
  2. We watch the universe. Once a minute, every tracked ticker is evaluated against your query. Tickers that transition from not-matching to matching since last evaluation are batched into a single webhook.fired event.
  3. You receive the event. Verify the HMAC signature, idempotently process the matches by X-Tickerbot-Delivery-Id, return 2xx within 10 seconds.

A ticker that's already in the match set isn't re-fired on subsequent evaluations. It only re-fires after exiting and re-entering — same state-change deduplication as the mobile product. There's no event for tickers exiting the match set; the API only fires on becoming-true.

Create a webhook

POST/v1/webhooks

Create a webhook that POSTs to your URL when tickers start matching.

Each webhook is a saved SQL `WHERE` clause plus a `target_url` we POST to. We evaluate the query once a minute against the live universe. If any tickers transitioned from not-matching to matching since the last evaluation, we send one POST containing all of them. Tickers that have already been matching aren’t re-fired until they exit the match set and re-enter (state-change deduplication, same as the mobile product). Webhooks are gated to the Reads + Webhooks plan or higher.

Authentication required
Body parameters
NameInTypeRequiredDescription
namebodystringrequired
Human-readable label. Helps you find this webhook later in `GET /v1/webhooks`. Doesn’t need to be unique.
Example: small-cap gappers
qbodystring (SQL WHERE clause)required
The condition to fire on. Same shape as `/v1/scan?q=`.
Example: gap_up_3pct AND volume_unusual_2x AND market_cap < 2000000000 AND NOT earnings_this_week
target_urlbodystring (https URL)required
HTTPS endpoint we POST events to. HTTP is rejected.
Example: https://yourapp.com/hooks/gappers
Responses
StatusDescription
201Webhook created. Response includes a `signing_secret` — store it; it is not shown again.
400SQL parse error, invalid URL, or invalid name.
403Webhooks require the Read+Write+Realtime plan.

Create a small-cap-gappers webhook

Request
curl -X POST https://api.tickerbot.io/v1/webhooks \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "small-cap gappers",
    "q": "gap_up_3pct AND volume_unusual_2x AND market_cap < 2000000000 AND NOT earnings_this_week",
    "target_url": "https://yourapp.com/hooks/gappers"
  }'
Response · 201
{
  "id": "wh_8f3a2b",
  "name": "small-cap gappers",
  "q": "gap_up_3pct AND volume_unusual_2x AND market_cap < 2000000000 AND NOT earnings_this_week",
  "target_url": "https://yourapp.com/hooks/gappers",
  "signing_secret": "whsec_5f8e4b1d3a2c9e7f6b5d4a3c2b1e0f9d",
  "status": "pending_verification",
  "created_at": "2026-04-30T14:40:00Z"
}

List webhooks

GET/v1/webhooks

List every webhook owned by the current API key.

Returns webhooks newest-first. `signing_secret` is omitted from list responses for security.

Authentication required
Query parameters
NameInTypeRequiredDescription
statusquerystringoptional
Filter by status. Allowed: `active`, `pending_verification`, `disabled`.
limitqueryintegeroptional
Max webhooks in this response.
Default: 100
Responses
StatusDescription
200List of webhooks.

List all webhooks

Request
curl https://api.tickerbot.io/v1/webhooks \
  -H "Authorization: Bearer YOUR_KEY"
Response · 200
{
  "count": 2,
  "webhooks": [
    {
      "id": "wh_8f3a2b",
      "name": "small-cap gappers",
      "q": "gap_up_3pct AND volume_unusual_2x AND market_cap < 2000000000 AND NOT earnings_this_week",
      "target_url": "https://yourapp.com/hooks/gappers",
      "status": "active",
      "created_at": "2026-04-30T14:40:00Z"
    },
    {
      "id": "wh_2c1d4e",
      "name": "MACD bullish crossovers",
      "q": "macd_above_signal AND momentum_strong_up",
      "target_url": "https://yourapp.com/hooks/macd",
      "status": "active",
      "created_at": "2026-04-28T09:12:33Z"
    }
  ]
}

Fetch one webhook

GET/v1/webhooks/{id}

Fetch one webhook by id.

Useful for inspection or display in your dashboard.

Authentication required
Path parameters
NameInTypeRequiredDescription
idpathstringrequired
Webhook identifier returned by `POST /v1/webhooks`.
Example: wh_8f3a2b
Responses
StatusDescription
200Webhook object.
404Webhook not found, or owned by a different key.

Get one webhook

Request
curl https://api.tickerbot.io/v1/webhooks/wh_8f3a2b \
  -H "Authorization: Bearer YOUR_KEY"
Response · 200
{
  "id": "wh_8f3a2b",
  "name": "small-cap gappers",
  "q": "gap_up_3pct AND volume_unusual_2x AND market_cap < 2000000000 AND NOT earnings_this_week",
  "target_url": "https://yourapp.com/hooks/gappers",
  "status": "active",
  "created_at": "2026-04-30T14:40:00Z"
}

Delete a webhook

DELETE/v1/webhooks/{id}

Delete a webhook.

Permanently removes the webhook. Pending retries are dropped. No soft-delete.

Authentication required
Path parameters
NameInTypeRequiredDescription
idpathstringrequired
Webhook identifier.
Example: wh_8f3a2b
Responses
StatusDescription
204Deleted. No response body.
404Webhook not found.

Delete a webhook

Request
curl -X DELETE https://api.tickerbot.io/v1/webhooks/wh_8f3a2b \
  -H "Authorization: Bearer YOUR_KEY"
Response · 204
(no body)

List delivery attempts

GET/v1/webhooks/{id}/deliveries

List delivery attempts for one webhook — when it fired, what we got back.

Every POST we send to your `target_url` is recorded as a delivery, including retries (the same `id` covers all attempts on a single event). Use this to debug: which events fired, did your endpoint accept them, what status code did we see, what error if any. Newest-first.

Authentication required
Path parameters
NameInTypeRequiredDescription
idpathstringrequired
Webhook identifier.
Example: wh_8f3a2b
Query parameters
NameInTypeRequiredDescription
statusquerystringoptional
Filter by delivery status. Allowed: `pending`, `delivered`, `permanent_failure`.
limitqueryintegeroptional
Max deliveries in this response.
Default: 50
Responses
StatusDescription
200List of deliveries (newest-first).
404Webhook not found, or owned by a different key.

List recent deliveries for a webhook

Request
curl https://api.tickerbot.io/v1/webhooks/wh_8f3a2b/deliveries \
  -H "Authorization: Bearer YOUR_KEY"
Response · 200
{
  "count": 2,
  "deliveries": [
    {
      "id": "dlv_a1b2c3d4e5f6g7h8",
      "webhook_id": "wh_8f3a2b",
      "event": "webhook.fired",
      "status": "delivered",
      "attempt": 1,
      "target_url": "https://yourapp.com/hooks/gappers",
      "last_status_code": 200,
      "last_error": null,
      "next_attempt_at": 1714485600,
      "created_at": 1714485600,
      "delivered_at": 1714485601
    },
    {
      "id": "dlv_x9y8z7w6v5u4t3s2",
      "webhook_id": "wh_8f3a2b",
      "event": "webhook.ping",
      "status": "delivered",
      "attempt": 1,
      "target_url": "https://yourapp.com/hooks/gappers",
      "last_status_code": 200,
      "last_error": null,
      "next_attempt_at": 1714485540,
      "created_at": 1714485540,
      "delivered_at": 1714485541
    }
  ]
}

Re-enable a disabled webhook

POST/v1/webhooks/{id}/enable

Re-enable a disabled or stuck webhook. Re-fires the verification ping.

A webhook flips to `disabled` after 5 failed delivery retries. Once you’ve fixed your endpoint, call this to put it back into rotation. We reset the verification handshake — status goes to `pending_verification`, we POST a fresh `webhook.ping`, and on a 2xx response within 10 seconds the webhook flips to `active`. Past match-state is cleared, so the next evaluation cycle treats every currently-matching ticker as a new match (you’ll get one `webhook.fired` payload with everything currently matching).

Authentication required
Path parameters
NameInTypeRequiredDescription
idpathstringrequired
Webhook identifier.
Example: wh_8f3a2b
Responses
StatusDescription
200Webhook re-enabled. Returns the updated webhook object.
404Webhook not found, or owned by a different key.

Re-enable a disabled webhook

Request
curl -X POST https://api.tickerbot.io/v1/webhooks/wh_8f3a2b/enable \
  -H "Authorization: Bearer YOUR_KEY"
Response · 200
{
  "id": "wh_8f3a2b",
  "name": "small-cap gappers",
  "q": "gap_up_3pct AND volume_unusual_2x AND market_cap < 2000000000 AND NOT earnings_this_week",
  "target_url": "https://yourapp.com/hooks/gappers",
  "status": "pending_verification",
  "created_at": "2026-04-30T14:40:00Z"
}

Delivery: headers

Every webhook delivery includes these headers. Inspect them in your handler to route events and verify authenticity.

HeaderDescription
Content-TypeAlways `application/json`.
User-AgentAlways `Tickerbot-Webhooks/1.0`.
X-Tickerbot-EventThe event name (`webhook.fired` or `webhook.ping`). Useful for routing in your handler.
X-Tickerbot-Delivery-IdUnique identifier for this delivery attempt. Stable across retries — use it for idempotency.
X-Tickerbot-Webhook-IdThe webhook that produced this event.
X-Tickerbot-SignatureHMAC-SHA256 signature in the form `t=<unix-timestamp>,v1=<hex-digest>`. The signed payload is `<timestamp>.<raw-body>` with the webhook’s `signing_secret` as the key.

Delivery: signature verification

HMAC-SHA256, Stripe-style. Reject any request that doesn’t verify.

The X-Tickerbot-Signature header has the form t=<unix-timestamp>,v1=<hex-digest>. To verify a delivery:

  1. Extract the timestamp and signature from the header.
  2. Concatenate <timestamp>, a literal ., and the raw request body.
  3. Compute HMAC-SHA256(signing_secret, payload) as a hex string.
  4. Compare with the v1 value using a constant-time comparison.
  5. Reject deliveries whose timestamp is more than 5 minutes from your server's current time — that's the replay window.
Node.js verification (sketch)
import crypto from 'crypto'

function verifyTickerbotSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(',').map((kv) => kv.split('='))
  )
  const ts = Number(parts.t)
  const sent = parts.v1
  if (Math.abs(Date.now() / 1000 - ts) > 300) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(sent, 'hex')
  )
}

Delivery: retry schedule

We retry failed deliveries at exponential intervals. After all attempts fail the webhook transitions to disabled.

AttemptDelay after previous
130 seconds
22 minutes
310 minutes
41 hour
56 hours

Retries reuse the same X-Tickerbot-Delivery-Id as the original attempt — use it to deduplicate on your side.

Event types

The shapes of every payload your endpoint may receive.

webhook.ping

A webhook is created. Sent once. If the target returns 2xx within 10 seconds, the webhook transitions from `pending_verification` to `active`.

FieldTypeDescription
eventstringAlways `webhook.ping`.
webhook_idstringThe webhook that just got created.
namestringThe webhook’s human-readable name.
as_oftimestamp (ISO 8601)When the webhook was created.
messagestringHuman-readable handshake message.
Example payload
{
  "event": "webhook.ping",
  "webhook_id": "wh_8f3a2b",
  "name": "small-cap gappers",
  "as_of": "2026-04-30T14:40:00Z",
  "message": "If you can read this, your endpoint is reachable."
}
webhook.fired

Tickers newly entered the webhook’s match set during the last evaluation cycle (one minute). One POST per evaluation cycle that has at least one new match — never an empty payload, never one POST per ticker. Tickers that were already matching at the previous evaluation are not re-fired.

FieldTypeDescription
eventstringAlways `webhook.fired`.
webhook_idstringThe webhook that produced this event.
namestringThe webhook’s human-readable name.
qstringThe WHERE clause that matched (snapshot at delivery time).
as_oftimestamp (ISO 8601)The evaluation cycle’s timestamp. The same value applies to every ticker in this payload.
matchesarrayThe tickers that just entered the match set. Each entry includes the ticker symbol, name, and the fields referenced in the WHERE clause at the moment of match.
Example payload
{
  "event": "webhook.fired",
  "webhook_id": "wh_8f3a2b",
  "name": "small-cap gappers",
  "q": "gap_up_3pct AND volume_unusual_2x AND market_cap < 2000000000 AND NOT earnings_this_week",
  "as_of": "2026-04-30T15:05:00Z",
  "matches": [
    {
      "ticker": "RIOT",
      "name": "Riot Platforms",
      "gap_up_3pct": true,
      "volume_unusual_2x": true,
      "market_cap": 1850000000,
      "earnings_this_week": false,
      "price": 14.22,
      "day_change_pct": 8.41
    },
    {
      "ticker": "MARA",
      "name": "Marathon Digital",
      "gap_up_3pct": true,
      "volume_unusual_2x": true,
      "market_cap": 1620000000,
      "earnings_this_week": false,
      "price": 22.10,
      "day_change_pct": 5.74
    }
  ]
}