Alert feed and webhooks
GET /v1/alerts/events returns the events your alert rules produced, oldest first, paged by cursor. This page also gives the webhook body and every event shape. The route appears in no OpenAPI spec.
Every alert rule you own produces events: one per transfer, fill, liquidation, index crossing or
liquidation-risk crossing it matched. An event is sent to the rule's channels and is also kept for 3 days in a
feed you can read, over REST at GET /v1/alerts/events or over MCP with
alerts_events. Rules are made with the MCP tools or under
Account → Alerts; what each rule type watches is on Alerts. This page is the contract
for what comes back: the feed route, the webhook body and the shape of each event.
This route is in no OpenAPI spec
GET /v1/alerts/events reads your own account's events, not one of the four data products, so it is not in
the OpenAPI document and the generated route tables elsewhere on this site do not list it. It is a real route on
https://data.vike.io, with the same key as the rest of /v1; this page is its reference.
Endpoint
GET https://data.vike.io/v1/alerts/events
GET https://data.vike.io/v1/alerts/eventsSend the same X-API-Key header as every other /v1 route (Authentication). The answer is
always about the key's own account: there is no parameter that names another user, and a rule_id that belongs to
someone else gives an empty page. A call costs 1 credit and counts against the hourly and per-minute limits like any
other /v1 call (Rate limits); a call that is answered with an error costs
nothing.
| Parameter | Where | Meaning |
|---|---|---|
cursor | query | The previous page's next_cursor, passed back verbatim. Omit it (or send it empty) to start at the oldest event still kept. It is opaque: do not build or edit one. |
limit | query | Events per page, 1 to 500, default 100. A number outside the range is clamped, not refused. |
rule_id | query | Only the events of this alert rule (its id from alerts_list). |
The answer
{
"events": [
{
"id": 1042,
"rule_id": 77,
"alert_label": "Whale BTC fills",
"alert_type": "hl_wallet",
"created_at": "2026-10-28T09:14:41.200Z",
"event": {
"schema": "vike.event.v1",
"event_id": "hl.fills:0x1111111111111111111111111111111111111111:1793178870482:123456789",
"source": "hl.fills",
"kind": "fill",
"ts": "2026-10-28T09:14:30.482Z",
"cursor": "1793178870482-00000000000123456789",
"chain": "hyperliquid",
"subject": { "wallet": "0x1111111111111111111111111111111111111111", "symbol": "BTC" },
"data": {
"action": "open", "side": "buy", "size_usd": 1250000.0, "size_token": 12.5, "price": 100000.0,
"closed_pnl": 0.0, "start_position": 0.0, "position_after": 12.5, "crossed": true,
"trade_id": 123456789, "dex": "HL"
},
"labels": { "pnl_cohort": "2x smart", "pnl_cohort_basis": "live", "pnl_cum_usd": 4210000.0, "size_cohort": "3x whale" }
},
"delivery": [ { "channel": "webhook", "channel_key": "12", "status": "sent" } ]
}
],
"next_cursor": "eyJpZCI6MTA0Mn0",
"has_more": false
}This example shows the shape; its values are made up. The wallet, ids and prices are not a real capture.
| Field | What it is |
|---|---|
events | The page, oldest first. Empty when there is nothing newer than cursor. |
next_cursor | Pass it back as cursor for the next page. An empty page returns the cursor you sent, so keep it and ask again. |
has_more | true when more events follow this page. |
Each element of events is one event of one rule:
| Field | What it is |
|---|---|
id | The feed's own row number for this (rule, event) pair. Not the event's id: use event.event_id for that. |
rule_id, alert_label, alert_type | The rule that matched, its name and its type. |
created_at | When the event was recorded, UTC, milliseconds, spelled like event.ts. |
event | What happened: exactly the event object the webhook carries, below. |
delivery | One entry per channel the event was sent to: channel (email or webhook), channel_key (the channel's id as alerts_list_channels shows it, or user_email for your account address) and status. An empty list means the rule has no channel: the feed is its channel. |
A delivery's status is pending (not tried yet), retry (a try failed, another is scheduled), sent, expired
(not delivered within 72 hours), skipped (the channel was deleted, disabled or changed before the send) or
unsupported (the channel cannot carry this event). It is read when you ask, so it moves on between polls.
Paging
- Events come oldest first, so a poller walks forward: pass
next_cursorback ascursor. - A page shorter than
limitwithhas_more: falsemeans you have caught up. Poll again later with the samenext_cursor; an empty page returns it unchanged. - An event is in the feed as soon as it is recorded, which is before its email or webhook is sent. Events appear in
the order they are numbered, so a poller that always passes back the last
next_cursormisses none and sees none twice. - Events are kept 3 days. A cursor older than that does not fail: the feed continues from the oldest event still kept.
- Deleting a rule for good (
alerts_deletewithhard: true) removes its events. Pausing it keeps them. - Only events of your own rules appear.
Errors
| Status | When |
|---|---|
400 | cursor is not a valid cursor. Pass back a next_cursor unchanged, or start again without one. |
401 | No credential, or one that is not valid. |
422 | limit or rule_id is not an integer. |
429 | The hourly budget or the per-minute cap is spent; see Rate limits. |
503 | The feed could not be read. Ask again. |
The webhook body
A webhook channel receives the same event as a POST. Every rule type uses one envelope; the signature and the
retries are on alerts_register_webhook.
{
"schema": "vike.alert.v1",
"alert_id": 77,
"alert_label": "Whale BTC fills",
"alert_type": "hl_wallet",
"triggered_at": "2026-10-28T09:14:30.482000+00:00",
"event": { "schema": "vike.event.v1", "event_id": "hl.fills:0x1111111111111111111111111111111111111111:1793178870482:123456789", "...": "as in the feed" }
}| Field | What it is |
|---|---|
schema | vike.alert.v1. |
alert_id, alert_label, alert_type | The rule: its id, its name (alert #<id> when it has none) and its type. |
triggered_at | The event's time, ISO 8601 with a +00:00 offset. |
event | The event, as in the feed. |
For a transfer event the body also carries the flat keys the earlier webhook format had, so a receiver written
for it keeps working: chain, token (the symbol, empty when the token has none), value_usd (null when the transfer has
no USD value), from_address, to_address and tx_hash. Those three are bare lowercase hex without 0x;
inside event the same values keep their 0x.
Two request headers identify the event: X-Vike-Event-Id (the event's event_id) and X-Vike-Attempt (1 for the first
try, then 2, 3 and so on). Delivery is at least once: a send that was cut off can arrive twice, so
dedupe on event_id.
Event shapes
Every event, in the feed or in a webhook, has the same ten fields, and kind says which data and subject to expect.
| Field | What it is |
|---|---|
schema | vike.event.v1. |
event_id | A unique string that always begins with its source and a colon. Stable: the same event always has the same id. At most 160 characters. |
source | chain.transfers, hl.fills, hl.liquidations, hl.indices or hl.liq_risk. |
kind | transfer, fill, liquidation, index or liq_risk. |
ts | When it happened, UTC, milliseconds: the block time, the fill time, the liquidation's minute or the position capture. |
cursor | A position within its source. Not the feed's cursor. |
chain | eth, bsc, base, hyperevm or hyperliquid. |
subject | Who or what it is about; keys by kind below. |
data | The facts of the event; keys by kind below. |
labels | The cohorts of subject.wallet when they were looked up: pnl_cohort, pnl_cohort_basis (live), pnl_cum_usd, size_cohort. A missing or null key means unknown, never a cohort. Fill events carry them. A liquidation event carries them only if they were looked up, which a cohort filter on a rule triggers; otherwise the object is empty. Index and liq_risk events carry none. |
kind | Rule types | subject | data |
|---|---|---|---|
transfer | transfer | from, to, token_address, token_symbol (null for an unmapped token), tx_hash | value_usd (null when unpriced), priced, block_number, log_index, tx_hash, transfer_type (native or erc20) |
fill | hl_wallet, hl_cohort_fill | wallet, symbol | action (open, close, flip, increase, reduce; null when it could not be worked out, possible only for hl_cohort_fill), side (buy or sell), size_usd, size_token, price, closed_pnl, start_position (signed, before the fill), position_after, crossed (the fill took liquidity), trade_id, dex |
liquidation | hl_liquidation | wallet, symbol | direction (long or short: the side of the position that was closed), size_usd, size_token, mark_price, inferred (always true) |
index | hl_cohort_bias | symbol | index, value, threshold, band, from_side and to_side (high or low), snapshot_ts |
liq_risk | hl_liq_proximity | wallet, symbol | asset, direction (long or short), liq_ratio, threshold, band, mark_price, entry_price, liquidation_price, size_usd, leverage, snapshot_ts |
Two things to know when you parse them. A fill's trade_id can be larger than 2^53, which JavaScript reads
inexactly: take the exact value from event_id (it ends with the id). And a liquidation is inferred from changes
between position captures, so its time is the minute it was found, not the instant of the liquidation; see
how soon an alert arrives.
Ask your agent
Connected to the Vike MCP server (https://data.vike.io/mcp) in Claude, ChatGPT or Cursor, ask it in plain words.
The alerts_events tool reads this feed, at 1 credit a call.
What have my alerts fired since yesterday?Live stream (WebSocket)
One authenticated WebSocket on data.vike.io that pushes closed Hyperliquid OHLCV bars as they complete. It spends the same hourly counter as REST and appears in no OpenAPI spec.
Schema and spec
The live OpenAPI document, its keyless copy, Swagger UI and ReDoc — where they live, what the spec actually declares, and the three gaps that decide whether you can generate a working client from it.