CRIUS Docs
API status Get API key
Real-time

Build Robinhood Chain price alerts with the WebSocket stream

Build Robinhood Chain price alerts on the Crius WebSocket price channel, with a runnable Node.js alert bot that checks liquidity and token security first.

Updated 3 min read
Short answer

Subscribe to price: plus the token address on wss://api.crius.sh/v1/robinhood/stream. You get a snapshot right away, then updates with usd, eth and liquidity_usd whenever the token trades (at most 10 a second). Compare usd with your thresholds, ignore updates with too little liquidity_usd, and check GET /v1/robinhood/token/{address}/security once before you alert. Free plan: 10 tokens per connection.

A price alert on Robinhood Chain is a rule (“tell me when AI crosses $0.10”, “tell me when TSLA moves 3% in 15 minutes”) checked against a live price. The Crius WebSocket gives you that price on the price:<token> channel: a snapshot as soon as you subscribe, then a new usd, eth and liquidity_usd every time the token trades. A good alert bot adds two checks the price alone can’t give it: enough liquidity behind the price, and a token that isn’t a trap.

What the price channel sends

Subscribe with {"op": "subscribe", "channels": ["price:0x2e8c31162b855a2ffa90f6f8634643ad6f111e18"]}. Right after the subscribed reply, the server sends the current price with "snapshot": true and no seq; after that, each update is a normal stream message.

Field Meaning
data.usd Price per whole token in USD
data.eth Price per whole token in ETH
data.liquidity_usd Liquidity of the pool the price came from (2 × its quote side)
block, ts The block and chain time (unix ms) of the trade that moved the price
snapshot true on the first message after subscribing

Updates are capped at 10 a second per token, and only come when the token trades. The stream reference covers the envelope, heartbeats and reorgs.

Check the price source before you rely on it

The REST price has the same usd, eth and liquidity_usd plus the pool, a confidence level and a thin-liquidity flag. Call it once when a user creates an alert, to decide whether the token is worth alerting on at all.

Artificial Inu's price and where it comes from

A user wants an alert on Artificial Inu (AI, 0x2e8c31162b855a2ffa90f6f8634643ad6f111e18). Is its price solid enough to alert on?

GET/v1/robinhood/token/0x2e8c31162b855a2ffa90f6f8634643ad6f111e18/price
curl "https://api.crius.sh/v1/robinhood/token/0x2e8c31162b855a2ffa90f6f8634643ad6f111e18/price"
Real response 200 Captured Oct 28, 2026 · 25 lines (arrays trimmed)
{
"data": {
"confidence": "high",
"dex": "uniswap_v4",
"eth": 0.0000341958,
"eth_usd": 2355.49,
"liquidity_usd": 657440,
"quote": "0x39dbed3a2bd333467115de45665cc57f813c4571",
"quote_liquidity_usd": 328720,
"source_pool": "0x230bc4a0fb5fb7ab27f6e72350dfa3f2489b0cdbaf34f0189d68672086ccb919",
"symbol": "AI",
"thin_liquidity": false,
"token": "0x2e8c31162b855a2ffa90f6f8634643ad6f111e18",
"usd": 0.0805479
},
"meta": {
"block": 100410151,
"block_age_ms": 315,
"block_ts": 1793225421000,
"chain": "robinhood",
"finalized_block": 100401329,
"lag_ms": 26,
"safe_block": 100405028
}
}

What this tells you

  • usd: 0.0805479 comes from a Uniswap v4 pool (dex: "uniswap_v4", source_pool is its 32-byte pool id), quoted in another token rather than ETH (quote is 0x39dbed3a2bd333467115de45665cc57f813c4571).
  • liquidity_usd: 657440 is 2 × the real quote side (quote_liquidity_usd: 328720). That’s the same number the stream sends in liquidity_usd, so a floor such as $10,000 works on both.
  • confidence: "high" and thin_liquidity: false: a single small trade can’t move this price far. For a token showing thin_liquidity: true, require a larger move or a second update before alerting.
Token price reference Runs from your browser on the anonymous plan Data looks wrong? Report it

The same token's security, checked once per alert

Before I tell users AI is pumping, is anything wrong with the token?

GET/v1/robinhood/token/0x2e8c31162b855a2ffa90f6f8634643ad6f111e18/security
curl "https://api.crius.sh/v1/robinhood/token/0x2e8c31162b855a2ffa90f6f8634643ad6f111e18/security"
Real response 200 Captured Oct 28, 2026 · 127 lines (arrays trimmed)
{
"data": {
"contract": {
"blacklist": false,
"can_change_limits": false,
"can_change_tax": false,
"cooldown": false,
"implementation": null,
"is_proxy": false,
"mintable": false,
"owner": "0xeb7c034704ef8dcd2d32324c1545f62fb4ad0862",
"ownership": "active",
"pausable": false,
"proxy_kind": null,
"self_destruct": false,
"trading_switch": false,
"upgradeable": false,
"verified": "none"
},
"flags": [
"ownership_active",
"unverified"
],
"honeypot": {
"is_honeypot": false,
"reason": null,
"selective": false
},
"hook": null,
"known_asset": false,
"launch": {
"flags": [],
"lp_top_wallet": {
"address": "0x9701fb0ade1e269c8f64ec0c7b3cfadb31a13a52",
"liquidity_usd": 703900,
"pct": 19.8021
},
"sniper_supply_pct": 1.70207,
"sniper_top_wallet_pct": 1.70207,
"sniper_wallets": 1,
"sniper_window_blocks": 10,
"uniform_holders": null,
"wallet_farm": null
},
"limits": {},
"liquidity": {
"status": "partially_locked",
"summary": {
"burned_pct": 14.4982,
"locked_pct": 0,
"next_unlock_ts": 1794049200000,
"protocol_fee_pct": 0,
"unlocked_in_wallets_pct": 73.5823,
"weighting": "by pool liquidity_usd"
}
},
"primary_dex": "uniswap_v4",
"risk": "low",
"score": 85,
"tax": {
"buy_pct": 0,
"confidence": "high",
"realized": {
"buy_pct": 0,
"by_wallet_class": {
"dev_sell_pct": null,
"others_sell_pct": 0
},
"last_sample_ts": 1793225667000,
"max_sell_pct_seen": 99.99,
"samples": {
"buys": 98750,
"sells": 101250
},
"sell_pct": 0,
"window": "30d"
},
"sell_pct": 0,
"simulated": {
"amount_eth": 0.042421762259500888,
"buy_gas": 145730,
"buy_pct": 0,
"can_buy": true,
"can_sell": true,
"can_transfer": true,
"from": "eoa",
"revert_reason": null,
"router": "0x89e5db8b5aa49aa85ac63f691524311aeb649eba",
"sell_gas": 126783,
"sell_pct": 0,
"transfer_pct": 0
},
"source": "realized",
"transfer_pct": 0
},
"tax_history": [
{
"from_block": 99878573,
"pct": 0,
"side": "buy"
},
{
"from_block": 99878935,
"pct": 100,
"side": "buy"
},
{
"from_block": 99880233,
"pct": 0,
"side": "buy"
}
],
"tax_history_total": 919,
"tax_mode": "dynamic",
"token": "0x2e8c31162b855a2ffa90f6f8634643ad6f111e18"
},
"meta": {
"block": 100412468,
"block_age_ms": 741,
"block_ts": 1793225673000,
"chain": "robinhood",
"finalized_block": 100401329,
"lag_ms": 5,
"safe_block": 100405028,
"simulation": "eth_simulateV1 via Uniswap V2 router at head"
}
}

What this tells you

  • score: 85, risk: "low", with ownership_active (the owner can still act on the contract) and unverified as the only flags.
  • No taxes: tax.buy_pct and tax.sell_pct are 0, measured over 98,750 buys and 101,250 sells (tax.realized.samples), and the simulated buy, transfer and sell all went through (tax.simulated.can_sell: true, via the Uniswap V2 router named in meta.simulation).
  • liquidity.status: "partially_locked": about 73.6% of the LP sits unlocked in wallets (unlocked_in_wallets_pct) and next_unlock_ts is 1794049200000 (November 7, 2026). A sharp drop could be liquidity leaving, so an alert bot can also subscribe to lp: for the token.
Security checks reference Runs from your browser on the anonymous plan Data looks wrong? Report it

An alert bot in Node.js

The bot reads rules, checks each token once, subscribes to their price channels and sends a message when a rule triggers. Each rule fires once, then re-arms after the price moves back past it. Delivery here is a webhook ({"content": ...} works with Discord webhooks); swap notify for email, Telegram or a push service.

// alerts.mjs: CRIUS_API_KEY=... WEBHOOK_URL=... node alerts.mjs (Node.js 22+)
const KEY = process.env.CRIUS_API_KEY;
const API = "https://api.crius.sh/v1/robinhood";
const MIN_LIQUIDITY_USD = 10_000;
const RULES = [
{ token: "0x2e8c31162b855a2ffa90f6f8634643ad6f111e18", name: "AI", above: 0.1 },
{ token: "0x2e8c31162b855a2ffa90f6f8634643ad6f111e18", name: "AI", below: 0.07 },
{ token: "0x322f0929c4625ed5bad873c95208d54e1c003b2d", name: "TSLA", movePct: 3, windowMs: 15 * 60_000 },
];
const blocked = new Set(); // tokens that failed the security check
const history = {}; // token -> [{ ts, usd }] for move rules
let backoff = 1000;
async function notify(text) {
console.log(text);
if (process.env.WEBHOOK_URL) {
await fetch(process.env.WEBHOOK_URL, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ content: text }) });
}
}
async function screen(token) {
const res = await fetch(`${API}/token/${token}/security`, { headers: { "x-api-key": KEY } });
const { data } = await res.json();
if (!data || data.risk === "critical" || data.honeypot?.is_honeypot) {
blocked.add(token);
console.warn(`not alerting on ${token}: ${data?.risk} ${data?.flags?.join(", ")}`);
}
}
function evaluate(token, p, ts) {
if (blocked.has(token) || p.liquidity_usd < MIN_LIQUIDITY_USD) return;
const h = (history[token] ??= []);
h.push({ ts, usd: p.usd });
while (h.length && h[0].ts < ts - 60 * 60_000) h.shift(); // keep one hour
for (const r of RULES.filter((r) => r.token === token)) {
let hit = false;
let why = "";
if (r.above != null) [hit, why] = [p.usd >= r.above, `above $${r.above}`];
if (r.below != null) [hit, why] = [p.usd <= r.below, `below $${r.below}`];
if (r.movePct != null) {
const base = h.find((x) => x.ts >= ts - r.windowMs)?.usd;
const pct = base ? ((p.usd - base) / base) * 100 : 0;
[hit, why] = [Math.abs(pct) >= r.movePct, `${pct.toFixed(2)}% in ${r.windowMs / 60_000} min`];
}
if (hit && !r.fired) notify(`${r.name} $${p.usd} (${why}), liquidity $${Math.round(p.liquidity_usd)}`).catch(console.error);
r.fired = hit; // re-arm once the condition is no longer true
}
}
function connect() {
const tokens = [...new Set(RULES.map((r) => r.token))].filter((t) => !blocked.has(t));
const ws = new WebSocket(`wss://api.crius.sh/v1/robinhood/stream?api_key=${KEY}`);
let idle;
const touch = () => {
clearTimeout(idle);
idle = setTimeout(() => ws.close(), 45_000);
};
ws.onopen = () => {
backoff = 1000;
touch();
ws.send(JSON.stringify({ op: "subscribe", channels: tokens.map((t) => `price:${t}`) }));
};
ws.onmessage = (e) => {
touch();
const m = JSON.parse(e.data);
if (m.ch === "error") return console.error(m.data);
if (!m.ch.startsWith("price:") || m.data.removed) return;
evaluate(m.ch.slice(6), m.data, m.ts ?? Date.now()); // the snapshot has no ts: use the clock
};
ws.onclose = () => {
clearTimeout(idle);
setTimeout(connect, backoff + Math.random() * 500);
backoff = Math.min(backoff * 2, 30_000);
};
ws.onerror = () => ws.close();
}
for (const t of new Set(RULES.map((r) => r.token))) await screen(t);
connect();
  1. Screen once, not per update. One security call per token (1 credit, or free without a key at 2 requests per second) when the alert is created. Re-check daily, or subscribe to tax: and lp: for the token to hear about tax changes and liquidity removal.

  2. Use the snapshot. After every reconnect the snapshot gives the current price immediately. A price alert doesn’t need resume: the latest price is what counts, and the snapshot is it.

  3. Floor the liquidity. Skip updates whose liquidity_usd is under your floor. Meme tokens often have one deep pool and several thin ones; the stream’s price comes from the deepest, but a token whose only pool is thin is easy to push across a threshold.

  4. Mind your subscription count. The hello message carries max_subscriptions. Past it, the server answers channel_denied with the channels it refused; split them across connections (Starter and up) or poll batch prices for the rest.

Frequently asked questions

How often does the price channel update?

Every time the token trades, at most once per 100 ms per token. A token that doesn't trade sends nothing, so a quiet price is not a stale connection: the 15-second heartbeat tells you the socket is alive.

How many tokens can one alert bot watch?

One price: subscription per token. The Free plan allows 10 subscriptions on 1 connection, Starter 100 on each of 3 connections, Pro 1,000 on each of 10. For a long watch list on a small plan, poll batch prices (100 tokens per 1-credit call) and stream only the tokens closest to a threshold.

Why did my alert fire on a price nobody can trade at?

A thin pool can print a price far from the real market. Each price message carries liquidity_usd (2 × the quote side of the pool the price came from): skip updates below a floor you choose. The REST price also returns confidence and thin_liquidity for a second look before you send.

Do price alerts need an API key?

The stream does: a Free key with the stream scope. The REST calls the bot makes (price, security, batch prices) also work without a key on the anonymous plan, at 2 requests per second.

Get your free Crius API key

Every example on this page works with a free key: higher rate limits than the anonymous plan, holders, wallets and the WebSocket stream. No card needed.

  • Robinhood Chain (4663) only, from our own archive nodes
  • REST + WebSocket
  • Pay on-chain or by card